c# - 如何在 WinForms 中等待信号同时监听事件?

标签 c# multithreading winforms events handler

案例一

这是我的设置。

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或来自 hereCoWaitForMultipleHandles ,他们就是这样做的。请记住,这种方法创建了一个嵌套的模式消息循环,可能的代码重入是最显着的暗示(详细讨论 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/

相关文章:

multithreading - 多线程视频解码器内存泄漏

c# - Form.Closing 未列为表单事件?

c# - 获取字符串作为变量名

c# - 在套接字读取之前替代 Thread.Sleep()

C# 覆盖 ToString 以便它在调试器中显示多行内容文本

c# - 在接口(interface)中创建通用属性

c# - 部分解密数据

python - 我该如何解决SyntaxError : positional argument follows keyword argument

java - 两个线程执行两个 `synchronized` 方法?

c# - 当您知道要分配的字段名称时,是否可以将数组分配给未知类型的数组?