2013-10-31 23 views
2

案例1如何等待WinForms中的信号并同时收听事件?

这是我的设置。

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(); 
} 

这工作,但似乎是对资源的浪费和黑客攻击。

基本上我想我需要一种方式wait但没有阻塞线程。这样做的正确方法是什么?

案例2

第二种情况有些类似(和相关的),并示出了同样的问题。

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的情况是一样的。我应该如何等待应继续为真

对不起,我想尽可能提供尽可能多的信息来帮助你。

[更新]

我被迫使用的.Net 2.0。

回答

2

对付它的最好方法是重新因子代码中使用async/await和转ApiStateUpdate事件为awaitable任务与TaskCompletionSourceEAP pattern)。

如果你真的要同步等待在UI线程的事件,从hereCoWaitForMultipleHandlesWaitWithDoEventshere,他们做到这一点。请记住,这种方法创建了一个嵌套的模式消息循环,可能的代码重入是最显着的含义(详细讨论here)。

[编辑]你在这里要做的是一个异步到同步的桥梁,这几乎总是一个坏主意。此外,我只是意识到你正在做一个构造函数。构造器本质上不应该有任何异步代码,它们是原子的。总是有一个更好的方法来将一个冗长的初始化过程分解出构造函数。 @StephenCleary在他非常翔实的blog post中谈到了这一点。

关于.NET 2.0的限制。虽然async/await可能是一个革命性的概念,但它背后的状态机概念并不是什么新鲜事。您始终可以通过一系列委托回调和事件来模拟它。匿名代表从.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(); 
} 
+0

我编辑了这个问题:限制到.Net 2.0。 – Yeseanul

+0

@Yeseanul,我已经更新了答案。 – Noseratio

+1

感谢您的回复。有两件事:'myObject.Initialized = delegate'应该使用'= ='而不是'=',并且'var'在.Net 2.0中不可用(也许你可以做这两个更改以便我可以接受答案)。我已经测试了第一个解决方案,它工作正常。 – Yeseanul

0

基本上我觉得我需要一种方法来等待,但不阻挡 线程。

你确实想阻塞线程,而不是UI线程。因此,简单地创建另一个线程并阻止它。使其简单并使用BackgroundWorker()控件。

+1

[Here](http://stackoverflow.com/questions/123661/how-to-wait-for-a-backgroundworker-to-cancel)是一些有趣的资源。但我找不出使用BackgroudWorker来解决问题的方法。也许我错过了一些东西...... – Yeseanul

+0

不......我想我误解了这个问题。我没有看到任何干净的方式来做到这一点。 –

1

你将不得不异步执行阻塞代码。否则,你会挂断不太好的UI线程。有多种不同的方法来做到这一点。这是一个使用新的asyncawait关键字。我承认,事实上,这是很多东西要吞并,只有在.NET 4.5+中才可行。

关于情况#1

首先,将您的EAP(基于事件的异步行话)入TAP(基于任务的异步模式)。这看起来很丑,因为EAP很难处理。您必须先订阅活动,然后在完成活动后取消订阅。

private Task<ApiObject> CreateApiObjectAsync() 
{ 
    bool cond1 = false; 
    bool cond2 = false; 

    var tcs = new TaskCompletionSource<ApiObject>(); 

    ApiObject instance = null; 
    ApiStateEventHandler handler = null; 

    handler = (who, howmuch) => 
    { 
     cond1 = cond1 || (who == "Something" && howmuch == 1); 
     cond2 = cond2 || (who == "SomethingElse" && howmuch == 1); 
     if (cond1 && cond2) 
     { 
     instance.ApiStateUpdate -= handler; 
     tcs.SetResult(instance); 
     } 
    } 

    var instance = new ApiObject(); 
    instance.ApiStateUpdate += handler; 
    return tcs.Task; 
} 

一旦你有了它,那么它会像这样使用。

internal class MyClass 
{ 
    private ApiObject apiObject; 

    internal MyClass() 
    { 
     InitializeAsync(); 
    } 

    private async Task InitializeAsync() 
    { 
     apiObject = await CreateApiObjectAsync(); 
     // At this point the instance is created and fully initialized. 
    } 
} 

我建议你读asynchronous initializationStephen Cleary的博客使用asyncawait你这样做,虽然之前。其实,通读他所有的Async OOP系列。这真的很好。

关于情况#2

在很多方面这种情况下更容易处理,因为构造函数和对象初始化不发挥作用。尽管如此,您仍然需要使用与上述相同的策略。首先,将API的EAP风格转换为TAP风格。如果这个等待条件不依赖于来自​​的事件,那么只需在TAP方法中需要任何等待逻辑,并定期评估它。最好不要忙着等待。然后await您创建的TAP方法。不要忘记在执行此操作时将Send标记为async

+0

感谢您的回复。非常彻底,但我不得不使用.Net 2.0(我也编辑了这个问题来反映这一点)。 – Yeseanul