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

时间:2012-09-19 20:16:31

标签: c# .net multithreading events

注意:我不控制UI代码。只有class PeriodicalThing代码。


考虑这种情况:

UI线程很乐意做UI内容,而另一个后台线程定期引发事件。某些UI订阅了这些事件,这意味着事件处理程序通常必须使用Invoke

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

  1. 用户定义的代码(即事件处理程序代码)在执行过程中不会异步中止
  2. 在清理功能返回后,没有更多的定期操作执行或执行
  3. 在清理功能返回后,不再执行或执行事件处理程序
  4. 我想出了一些东西,但是有一个僵局。死锁基本上是用户定义代码中的一个错误,其中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();
        }
    }
    

    ...

    // user-defined code; consider this immutable
    class Form1 : Form
    {
        .ctor()
        {
            ...
            this.PeriodicalThing.ThingsHappened += this.ThingsHappenedHandler
        }
    
        private void ThingsHappenedHandler(object sender, EventArgs e)
        {
            if (this.InvokeRequired) // actually always true
            {
                // periodical thread will deadlock here
                this.Invoke(
                    new Action<object, EventArgs>(this.ThingsHappenedHandler), sender, e)
                    );
                return;
            }
            this.listBox1.Items.Add("things happened");
        }
    
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            this.PeriodicalThing.CleanUp();
        }
    }
    

    当UI线程由于发生在UI线程上的FormClosing之类的事件而关闭时,它将触发清理。当后台线程此时发出Invoke时,Invoke必须阻塞,直到UI线程完成其当前事件处理程序(首先触发清理)。此外,清理操作需要等待后台线程(以及当前的Invoke)终止,从而导致死锁。


    最佳解决方案是在Thread.Join()中断UI线程,让所有等待的Invokes执行,然后返回Thread.Join()。但对C#来说,这似乎是不可能的。也许有人有一个疯狂的想法,我如何使用一些帮助线程将清理方法从UI线程移开,但我不知道我会怎么做。

3 个答案:

答案 0 :(得分:1)

问题出在这里

public void CleanUp()
{
    this.abort = true;
    // ui thread will deadlock here
    this.PeriodicalThread.Join();  // just delete this
}

Join()将阻止(!)调用线程。而这又会阻止所有Invoke操作。

这枚硬币的另一部分是

{
  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");
 }

BeginInvoke是对Invoke的改进,只要你只返回void并且不重载MessageLoop。

但是要摆脱Join()。它没用。

编辑,因为某些部分不在您的控制之下:

以下确实是答案,而且可能:

  

在Thread.Join()中断UI线程并让所有等待的Invokes执行,然后返回Thread.Join()

public void CleanUp()
{
    this.abort = true;

    while (! this.PeriodicalThread.Join(20)) 
    { 
       Application.DoEvents(); 
    }
}

这不会死锁,但使用Application.DoEvents()时会出现问题。你必须检查所有其他事件(FormClosing)中发生了什么,这也不在你的控制之下......
它可能会起作用,但需要进行一些严格的测试。


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

protected virtual void OnThingsHappaned(EventArgs e)
{
    var handler = ThingsHappened;

    if (handler  != null)
    {
        handler (this, e);
    }
}

答案 1 :(得分:0)

我不建议重写,但我建议您使用BackgroundWorker吗?

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

可在此处找到快速操作方法: http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx

答案 2 :(得分:0)

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

BackgroundWorker.WorkerSupportsCancellation Property

我用它来锤击它,懒得加载一个动态创建的昂贵的FlowDocument。如果他们点击下一个文件需要取消该工作并从下一个开始。

抱歉,我刚看到其他BackGroundWorker以及您无法使用它的评论。我没有在UI级别上使用它。我甚至没有使用WinForms。我在商业/数据层使用。