案例一
这是我的设置。
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
对象会被创建,该方法需要等待ApiNotifyHandler
执行(即, ApiFound
事件发生)在调用 ApiSend()
之前。
所以这与案例 1 中的情况相同。我应该如何等待 shouldContinue 为真?
很抱歉这个问题很长,但我想提供尽可能多的信息来帮助你帮助我。
[更新]
我不得不使用 .Net 2.0。
最佳答案
处理此问题的最佳方法是重构您的代码以使用 async/await
并将 ApiStateUpdate
事件转换为具有 TaskCompletionSource< 的可等待任务
(EAP pattern)。
如果您真的想同步等待 UI 线程上的事件,请查看 here 中的 WaitWithDoEvents
或来自 here 的 CoWaitForMultipleHandles
,他们就是这样做的。请记住,这种方法创建了一个嵌套的模式消息循环,可能的代码重入是最显着的暗示(详细讨论 here)。
[已编辑] 您在这里尝试做的是一个异步到同步的桥梁,它本身几乎总是一个坏主意。此外,我刚刚意识到您正在构造函数中执行此操作。构造函数,就其本质而言,内部不应该有任何异步代码,它们是原子的。总是有更好的方法将冗长的初始化过程从构造函数中分解出来。 @StephenCleary 在他非常翔实的 blog post 中谈到了这一点.
关于 .NET 2.0 限制。虽然 async/await
可能是一个革命性的概念,但其背后的状态机概念并不是什么新鲜事。您始终可以使用一系列委托(delegate)回调和事件来模拟它。匿名委托(delegate)自 .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
(来自 here,基于 MsgWaitForMultipleObjects
)实现异步到同步的桥接,它可能看起来像这样:
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();
}
关于c# - 如何在 WinForms 中等待信号同时监听事件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/19696649/