情况1
这是我的设置.
internal class MyClass
{
private ApiObject apiObject;
private bool cond1;
private bool cond2;
internal MyClass()
{
this.apiObject = new ApiObject();
this.apiObject.ApiStateUpdate += new ApiStateUpdateEventHandler(ApiStateHandler);
//wait for both conditions to be true
}
private void ApiStateHandler(string who, int howMuch)
{
if(who.Equals("Something") && howMuch == 1)
this.cond1 = true;
else if(who.Equals("SomethingElse") && howMuch == 1)
this.cond2 = true;
}
}
如何等待两个条件都成立?
如果我做:
while(!(this.cond1 && this.cond2))
{
System.Threading.Thread.Sleep(1000);
}
ApiStateHandler()中的代码似乎永远不会执行.
如果我做:
while(!(this.cond1 && this.cond2))
{
System.Windows.Forms.Application.DoEvents();
}
这行得通,但似乎浪费资源和黑客.
基本上,我认为我需要一种等待但又不阻塞线程的方法.这样做的正确方式是什么?
情况二
第二种情况有些相似(相关),并说明了相同的问题.
internal class MyClass
{
private ApiNotifyClass apiNotifyClass;
private shouldContinue = false;
internal MyClass()
{
//in addition to the code from above
this.apiNotifyClass = new ApiNotifyClass();
this.apiNotifyClass.ApiFound += ApiNofityFoundEventHandler(ApiNotifyHandler);
}
internal void Send(SomethingToSend somethigToSend)
{
Verifyer verifier = this.apiObject.ApiGet(somethingToSend);
this.apiNotifyClass.ApiAttach(verifier);
//wait for the shouldContinue to be true
this.apiObject.ApiSend(verifier);
this.apiNotifyClass.ApiDetach(verifier);
}
private void ApiNotifyHandler()
{
this.shouldContinue = true;
}
}
调用Send()时,将创建Verifier对象,并且该方法需要在调用ApiSend()之前等待ApiNotifyHandler执行(即,发生ApiFound事件).
因此,这与案例1中的情况相同.我应该如何等待shouldContinue为真?
抱歉,很长的问题,但是我想提供尽可能多的信息来帮助您.
[更新]
我*使用.Net 2.0.
解决方法:
解决此问题的最佳方法是将代码重构为使用async / await,并将ApiStateUpdate事件转换为带有TaskCompletionSource(EAP pattern)的可等待任务.
如果您真的想同步等待UI线程上的事件,请查看here中的WaitWithDoEvents或here中的CoWaitForMultipleHandles,它们就是这样做的.请记住,这种方法创建了一个嵌套的模态消息循环,其中可能的代码重入是最显着的含义(在详细信息here中进行了讨论).
[编辑]您想要在这里做的是异步到同步桥,这几乎总是一个坏主意.而且,我刚刚意识到您正在构造器中执行此操作.就其本质而言,构造函数内部不应包含任何异步代码,它们是原子的.总有一种更好的方法可以从构造函数中排除冗长的初始化过程. @StephenCleary在他非常有用的blog post中谈到了这一点.
关于.NET 2.0限制.尽管异步/等待可能是一个革命性的概念,但其背后的状态机概念并不是什么新鲜事物.您始终可以使用一系列委托回调和事件来模拟它.自.NET 2.0开始,匿名代表就在那里.例如,您的代码可能如下所示:
internal class MyClass
{
private ApiObject apiObject;
public event EventHandler Initialized;
internal MyClass()
{
this.apiObject = new ApiObject();
}
public void Initialize()
{
ApiStateUpdateEventHandler handler = null;
handler = delegate(string who, int howMuch)
{
bool cond1 = false;
bool cond2 = false;
if(who.Equals("Something") && howMuch == 1)
cond1 = true;
else if(who.Equals("SomethingElse") && howMuch == 1)
cond2 = true;
//wait for both conditions to be true
if ( !cond1 && !cond2 )
return;
this.apiObject.ApiStateUpdate -= handler;
// fire an event when both conditions are met
if (this.Initialized != null)
this.Initialized(this, new EventArgs());
};
this.apiObject.ApiStateUpdate += handler;
}
}
使用MyClass的客户端代码可能如下所示:
MyClass myObject = new MyClass();
myObject.Initialized += delegate
{
MessageBox.Show("Hello!");
};
myObject.Initialize();
上面是.NET 2.0正确的基于异步事件的模式.一个更简单但更糟糕的解决方案是使用WaitWithDoEvents(基于MsgWaitForMultipleObjects,从here开始)来实现异步到同步桥,如下所示:
internal class MyClass
{
private ApiObject apiObject;
internal MyClass()
{
this.apiObject = new ApiObject();
Initialize();
}
private void Initialize()
{
using (ManualResetEvent syncEvent = new ManualResetEvent())
{
ApiStateUpdateEventHandler handler = null;
handler = delegate(string who, int howMuch)
{
bool cond1 = false;
bool cond2 = false;
if(who.Equals("Something") && howMuch == 1)
cond1 = true;
else if(who.Equals("SomethingElse") && howMuch == 1)
cond2 = true;
//wait for both conditions to be true
if ( !cond1 && !cond2 )
return;
this.apiObject.ApiStateUpdate -= handler;
syncEvent.Set();
};
this.apiObject.ApiStateUpdate += handler;
WaitWithDoEvents(syncEvent, Timeout.Infinite);
}
}
}
但是,这比您的问题中的繁忙等待循环还要有效:
while(!(this.cond1 && this.cond2))
{
System.Windows.Forms.Application.DoEvents();
}