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