C# 使用Invoke异步引发的事件会导致多线程问题

C# 使用Invoke异步引发的事件会导致多线程问题,c#,.net,multithreading,events,C#,.net,Multithreading,Events,注意:我不控制UI代码。仅限类周期设置代码 考虑以下情况: UI线程正在愉快地执行UI任务,而另一个后台线程则定期引发事件。事件由某些UI订阅,这意味着事件处理程序通常必须使用Invoke 我需要的是一种以安全的方式执行清理操作(清理基本上意味着停止后台线程)的方法,它保证: 在执行期间中不异步中止用户定义代码(即事件处理程序代码) 清理功能返回后,不再执行或执行定期操作 清理函数返回后,将不再执行或执行更多事件处理程序 我想出了一些办法,但出现了僵局。死锁基本上是用户定义代码中的一个错误,使

注意:我不控制UI代码。仅限
类周期设置
代码


考虑以下情况:

UI线程正在愉快地执行UI任务,而另一个后台线程则定期引发事件。事件由某些UI订阅,这意味着事件处理程序通常必须使用
Invoke

我需要的是一种以安全的方式执行清理操作(清理基本上意味着停止后台线程)的方法,它保证:

  • 在执行期间
  • 中不异步中止用户定义代码(即事件处理程序代码)
  • 清理功能返回后,不再执行或执行定期操作
  • 清理函数返回后,将不再执行或执行更多事件处理程序
  • 我想出了一些办法,但出现了僵局。死锁基本上是用户定义代码中的一个错误,使用
    BeginInvoke
    可以解决这个问题,但如果出现非平凡的编程错误,直接死锁是不可取的。
    还要注意的是,
    BeginInvoke
    仅在
    FormClosing
    场景中起作用,因为表单的调用列表恰好在
    FormClosing
    之后被清除;这似乎是一致的,但我还没有发现它的文件

    如果有解决方案,显然不明显,但可能我错过了一个窍门。我不敢相信以前没有人遇到过类似的问题

    class PeriodicalThing
    {
        bool abort = false;
        Thread PeriodicalThread;
        ...
        PeriodicalThreadProc()
        {
            while (!this.abort)
            {
                DoStuff();
                OnThingsHappened(new EventArgs(...));
            }
        }
    
        public event EventHandler<EventArgs> ThingsHappened;
        protected virtual void OnThingsHappaned(EventArgs e)
        {
            // update -- oversight by me - see Henk Holterman's answer
            var handler = this.ThingsHappened;
            if (handler != null)
            {
                handler(this, e);
            }
        }
    
        public void CleanUp()
        {
            this.abort = true;
            // ui thread will deadlock here
            this.PeriodicalThread.Join();
        }
    }
    
    当UI线程由于发生在UI线程上的事件(如
    FormClosing
    )而关闭时,它将触发清理。当后台线程此时发出
    Invoke
    时,
    Invoke
    必须阻塞,直到UI线程完成其当前事件处理程序(首先触发清理)。此外,清理操作需要等待后台线程(以及当前调用)终止,从而导致死锁


    最佳解决方案是在
    thread.Join()
    处中断UI线程,让所有等待的调用执行,然后返回
    thread.Join()
    。但这在我看来是不可能的。也许有人有一个疯狂的想法,我可以使用一些helper线程将清理方法从UI线程移开,但我不知道我该怎么做。

    问题就在这里

    public void CleanUp()
    {
        this.abort = true;
        // ui thread will deadlock here
        this.PeriodicalThread.Join();  // just delete this
    }
    
    Join()
    将阻止(!)调用线程。这反过来会阻止所有调用操作

    硬币的另一部分是

    {
      if (this.InvokeRequired) // actually always true
        {
            // periodical thread will deadlock here
        //    this.Invoke(
            this.BeginInvoke(            // doesn't wait so it doesn't block
                new Action<object, EventArgs>(this.ThingsHappenedHandler), sender, e)
                );
            return;
        }
        this.listBox1.Items.Add("things happened");
     }
    
    这不会死锁,但使用
    Application.DoEvents()
    时会出现问题。您必须检查所有其他事件(FormClosing)中发生的情况,这些事件也不在您的控制范围之内……
    它可能会工作,但需要一些严格的测试


    由于要混合线程和事件,请使用以下模式:

    protected virtual void OnThingsHappaned(EventArgs e)
    {
        var handler = ThingsHappened;
    
        if (handler  != null)
        {
            handler (this, e);
        }
    }
    

    我不想建议重新编写,但我可以建议使用
    后台工作人员

    我发现它是一个快速简单的异步进程,您可以向调用线程发回有关其状态的消息(如百分比或状态对象)

    可在此处找到快速操作方法:

    不确定这是否适合您的情况,但我使用BackGroundWorker和SupportCancellation。如果我不在,请发表评论,我将删除

    我用它来延迟加载一个昂贵的动态创建的流程文档。如果他们单击下一个文档,则需要取消该工作并开始下一个文档


    抱歉,我刚刚看到另一个BackGroundWorker和您的评论,您不能使用它。我没有在UI级别上使用它。我甚至不使用WinForms。我在业务/数据层使用。

    如问题所述,
    调用
    是用户代码的一部分,不在我的控制之下。此外,
    Join
    也不是无用的。它满足条件2和3。好的,我没有仔细阅读。通过循环执行
    while(!otherThread.Join(someShortTime)){Application.DoEvents();}
    可以满足所有这些要求,但这也有问题。我需要检查
    DoEvents
    在我可能要处理的某些边缘情况下的行为,但听起来很有用,谢谢。后台工作人员是WinForms UI的一部分,因为他们使用
    WinFormsSynchronizationContext
    ,因此是我无法控制的用户代码的一部分。BackgroundWorker类位于System.ComponentModel中。
    public void CleanUp()
    {
        this.abort = true;
     
        while (! this.PeriodicalThread.Join(20)) 
        { 
           Application.DoEvents(); 
        }
    }
    
    protected virtual void OnThingsHappaned(EventArgs e)
    {
        var handler = ThingsHappened;
    
        if (handler  != null)
        {
            handler (this, e);
        }
    }