异步引发使用Invoke引发多线程问题的事件 [英] Asynchronously raised events which use Invoke causing problems with multithreading

查看:222
本文介绍了异步引发使用Invoke引发多线程问题的事件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

通知:我不控制UI代码。只有类PeriodicalThing 代码。






考虑这种情况: / p>

UI线程很乐意做UI的东西,而有另一个后台线程会定期引发事件。事件由一些UI订阅,这意味着事件处理程序通常必须使用 Invoke



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


  1. 用户定义的代码(即事件处理程序代码)在执行过程中不会异步中止

  2. 在清理函数返回后,执行或执行的定期操作不再有

  3. 清理功能返回后不再执行或执行任何事件处理程序

我想出了有什么,但是有一个僵局。死锁基本上是用户定义的代码中的错误,其中 BeginInvoke 的使用可能会解决问题,但是在非平凡编程的情况下是一个直接的死锁错误是不行的。

另请注意, BeginInvoke 只适用于 FormClosing 方案,因为在 FormClosing 之后,窗体的调用列表恰好被清除;一些似乎是一致的,但我还没有找到记录。



如果 有一个解决方案这显然不明显,但也许我错过了一个技巧。我不敢相信以前没有遇到类似的问题。

  class PeriodicalThing 
{
bool abort = false;
线程PeriodicalThread;
...
PeriodicalThreadProc()
{
while(!this.abort)
{
DoStuff();
OnThingsHappened(新的EventArgs(...));
}
}

公共事件EventHandler< EventArgs> ThingsHappened;
protected virtual void OnThingsHappaned(EventArgs e)
{
//更新 - 由我监督 - 看Henk Holterman的答案
var handler = this.ThingsHappened;
if(handler!= null)
{
handler(this,e);
}
}

public void CleanUp()
{
this.abort = true;
// ui线程将在这里死锁
this.PeriodicalThread.Join();
}
}

...

  //用户定义的代码;考虑这个不可变的
类Form1:Form
{
.ctor()
{
...
this.PeriodicalThing.ThingsHappened + = this.ThingsHappenedHandler
}

private void ThingsHappenedHandler(object sender,EventArgs e)
{
if(this.InvokeRequired)//实际上一直为true
{
//期刊线程将在这里死锁
this.Invoke(
new Action< object,EventArgs>(this.ThingsHappenedHandler),sender,e)
);
返回;
}
this.listBox1.Items.Add(事情发生);
}

private void Form1_FormClosing(object sender,FormClosingEventArgs e)
{
this.PeriodicalThing.CleanUp();
}
}

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






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

解决方案

问题在这里

  public void CleanUp()
{
this.abort = true;
// ui线程将在这里死锁
this.PeriodicalThread.Join(); //只是删除这个
}

Join() / code>将阻塞(!)调用线程。而这又阻止了所有的Invoke动作。



此硬币的另一部分是

  {
if(this.InvokeRequired)//实际上总是真的
{
//期刊线程将在这里死锁
// this.Invoke(
this.BeginInvoke(// doesn' t等待,所以它不阻止
new Action< object,EventArgs>(this.ThingsHappenedHandler),sender,e)
);
返回;
}
this.listBox1.Items.Add(事情发生);
}

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



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



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



以下是确实的答案,这是可能的:


在Thread.Join()中断UI线程,让所有等待的Invoke执行,然后回到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);
}
}


Notice: I do not control the UI code. Only the class PeriodicalThing code.


Consider this scenario:

The UI thread is happily doing UI stuff, while there is another background thread which periodically raises events. The events are subscribed to by some UI, which means the event handlers generally have to use Invoke.

What I need is a way to perform the clean up operation (cleaning up means stopping the background thread, basically) in safe manner, that guarantees:

  1. User-defined code (i.e. event handler code) is not asynchronously aborted in the middle of execution
  2. There are no more periodical operations executed or executing after the clean up function has returned
  3. No more event handlers will be executed or executing after the clean up function has returned

I came up with something, but there is a deadlock. The deadlock is basically an error in the user-defined code, where the usage of BeginInvoke could have fixed the problem, but a straight out deadlock in case of a non-trivial programming error isn't the way to go.
Also note that BeginInvoke only happens to work in the FormClosing scenario because the invocation list of a Form happens to be cleared after FormClosing; something that seems to be consistent but I haven't found it documented yet.

If there is a solution, it's apparently not obvious, but maybe I'm missing out a trick. I can't believe noone has run into a similar problem before.

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

When the UI thread is shutting down due to an event such as FormClosing, which happened on the UI thread, then it will trigger the clean up. When the background thread issues an Invoke at this time, the Invoke has to block until the UI thread is finished with its current event handler (which triggered the clean up in the first place). Also, the clean up operation needs to wait for the background thread (and thus the current Invoke) to terminate, causing a deadlock.


The optimal solution would be to interrupt the UI thread at Thread.Join() and let all the waiting Invokes execute, and then going back to Thread.Join(). But that seems impossible to me with C#. Maybe someone has a crazy idea how I could use some helper threads to move the clean up method away from the UI thread, but I don't know how I would do that.

解决方案

The problem is here

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

The Join() will block (!) the calling thread. And that in turn blocks all Invoke actions.

The other part of this coin is

{
  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 is an improvement over Invoke anyway, as long as you only return void and do not overload the MessageLoop.

But do get rid of the Join(). It has no use.

Edit, since some parts are not under your control:

The following is indeed the answer and it's possible:

to interrupt the UI thread at Thread.Join() and let all the waiting Invokes execute, and then going back to Thread.Join()

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

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

This won't deadlock but there are issues with using Application.DoEvents(). You'll have to check what happens in all other events (FormClosing) and that's not under your control too...
It'll probably work but some rigorous testing is called for.


Since you are mixing threads and events, use this pattern:

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

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

这篇关于异步引发使用Invoke引发多线程问题的事件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆