如何在WinForms中等待信号同时还要收听事件?

时间:2013-10-31 01:02:14

标签: c# multithreading winforms events handler

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

这可行,但似乎浪费资源和黑客。

基本上我认为我需要一种方法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对象,并且该方法需要等待ApiNotifyHandler执行(即ApiFound事件发生)致电ApiSend()

所以这与案例1 中的情况相同。我应该如何等待shouldContinue为真

很抱歉这个问题很长,但我想尽可能多地提供信息来帮助你。

[更新

我被迫使用.Net 2.0。

3 个答案:

答案 0 :(得分:2)

解决此问题的最佳方法是重新设计代码以使用async/await并将ApiStateUpdate事件转换为TaskCompletionSourceEAP pattern)的等待任务。

如果您真的想要同步等待UI线程上的事件,请查看来自hereWaitWithDoEvents或来自hereCoWaitForMultipleHandles,他们就是这么做的。请记住,这种方法创建了一个嵌套的模态消息循环,可能的代码重入是最显着的含义(详细讨论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(来自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();
}

然而,这仍然比你问题中忙碌的等待循环更有效:

{{1}}

答案 1 :(得分: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.
    }
}

我建议您先使用asyncawaitStephen Cleary上阅读asynchronous initialization的博客,然后再执行此操作。实际上,阅读他所有的Async OOP系列。这真的很棒。

关于案例#2

在很多方面,这种情况更容易处理,因为构造函数和对象初始化不起作用。您仍然需要使用与上述相同的策略。首先,将API的EAP样式转换为TAP样式。如果此等待条件不依赖于来自ApiObject的事件,那么恰好在TAP方法中需要等待逻辑并定期评估它。优选地,这可以在不进行繁忙等待的情况下完成。然后await您创建的TAP方法。不过,在执行此操作时,请不要忘记将Send标记为async

答案 2 :(得分:0)

  

基本上我认为我需要一种方法来等待,但不会阻止   线程。

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