注意:我不控制UI代码。只有class PeriodicalThing
代码。
考虑这种情况:
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();
}
}
...
// 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线程移开,但我不知道我会怎么做。
答案 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。我在商业/数据层使用。