wpf 当线程使用调度器并且主线程正在等待线程完成时死锁
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/24211934/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Deadlock when thread uses dispatcher and the main thread is waiting for thread to finish
提问by seguso
Can someone please explain why this creates a deadlock, and how to solve it?
有人可以解释为什么这会造成僵局,以及如何解决它?
txtLog.AppendText("We are starting the thread" + Environment.NewLine);
var th = new Thread(() =>
{
Application.Current.Dispatcher.Invoke(new Action(() => // causes deadlock
{
txtLog.AppendText("We are inside the thread" + Environment.NewLine); // never gets printed
// compute some result...
}));
});
th.Start();
th.Join(); // causes deadlock
// ... retrieve the result computed by the thread
Explanation: I need my secondary thread to compute a result, and to return it to the main thread. But the secondary thread must also write debug informations to the log; and the log is in a wpf window, so the thread needs to be able to use the dispatcher.invoke(). But the moment I do Dispatcher.Invoke, a deadlock occurs, because the main thread is waiting for the secondary thread to finish, because it needs the result.
说明:我需要我的辅助线程来计算结果,并将其返回到主线程。但是辅助线程也必须将调试信息写入日志;并且日志在 wpf 窗口中,因此线程需要能够使用 dispatcher.invoke()。但是在我执行 Dispatcher.Invoke 的那一刻,发生了死锁,因为主线程正在等待辅助线程完成,因为它需要结果。
I need a pattern to solve this. Please help me rewrite this code. (Please write actual code, do not just say "use BeginInvoke"). Thank you.
我需要一个模式来解决这个问题。请帮我重写这段代码。(请编写实际代码,不要只说“使用 BeginInvoke”)。谢谢你。
Also, theoretically, I don't understand one thing: a deadlock can only happen when two threads access two shared resourcesin different orders. But what are the actual resources in this case? One is the GUI. But what is the other? I can't see it.
另外,理论上,我不明白一件事:只有当两个线程以不同的顺序访问两个共享资源时,才会发生死锁。但在这种情况下,实际资源是什么?一种是图形用户界面。但另一个是什么?我看不到。
And the deadlock is usually solved by imposing the rule that the threads can only lock the resources in a precise order. I've done this already elsewhere. But how can I impose this rule in this case, since I don't understand what the actual resources are?
而死锁通常是通过强加线程只能以精确的顺序锁定资源的规则来解决的。我已经在其他地方这样做了。但是在这种情况下我怎么能强加这个规则,因为我不明白实际的资源是什么?
回答by Jairo Andres Velasco Romero
Short answer: use BeginInvoke()instead of Invoke().
Long answer change your approach: see the altenative.
简短回答:使用BeginInvoke()而不是Invoke(). 长答案改变你的方法:见替代。
Currently your Thread.Join()is causing that main thread get blocked waiting for the termination of secondary thread, but secondary thread is waiting to main thread executes your AppendText action, thus your app is deadlocked.
当前,您Thread.Join()导致主线程被阻塞,等待辅助线程终止,但辅助线程正在等待主线程执行您的 AppendText 操作,因此您的应用程序已死锁。
If you change to BeginInvoke()then your seconday thread will not wait until main thread executes your action. Instead of this, it will queue your invocation and continues. Your main thread will not blocked on Join() because your seconday thread this time ends succesfully. Then, when main thread completes this method will be free to process the queued invocation to AppendText
如果您更改为,BeginInvoke()那么您的辅助线程将不会等到主线程执行您的操作。取而代之的是,它将对您的调用进行排队并继续。您的主线程不会在 Join() 上阻塞,因为这次您的辅助线程成功结束。然后,当主线程完成时,此方法将自由处理对 AppendText 的排队调用
Alternative:
选择:
void DoSomehtingCool()
{
var factory = new TaskFactory(TaskScheduler.FromCurrentSynchronizationContext());
factory.StartNew(() =>
{
var result = await IntensiveComputing();
txtLog.AppendText("Result of the computing: " + result);
});
}
async Task<double> IntensiveComputing()
{
Thread.Sleep(5000);
return 20;
}
回答by SLaks
This deadlock happens because the UI thread is waiting for the background thread to finish, and the background thread is waiting for the UI thread to become free.
发生这种死锁是因为 UI 线程正在等待后台线程完成,而后台线程正在等待 UI 线程空闲。
The best solution is to use async:
最好的解决方案是使用async:
var result = await Task.Run(() => {
...
await Dispatcher.InvokeAsync(() => ...);
...
return ...;
});
回答by Yuval Itzchakov
The Dispatcheris trying to execute work in the UI message loop, but that same loop is currently stuck on th.Join, hence they are waiting on each other and that causes the deadlock.
该Dispatcher正试图在UI消息循环执行工作,但同样的循环,目前停留在th.Join,因此他们正在等待对方,这导致死锁。
If you start a Threadand immediately Joinon it, you definitely have a code smell and should re-think what you're doing.
如果你Thread立即开始Join,你肯定有代码味道,应该重新考虑你在做什么。
If you want things to be done without blocking the UI you can simply awaiton InvokeAsync
如果你想在不阻塞UI,你可以简单地做的事情await上InvokeAsync
回答by UML
I had a similar problem which I finally solved in this way:
我有一个类似的问题,我最终以这种方式解决了:
do{
// Force the dispatcher to run the queued operations
Dispatcher.CurrentDispatcher.Invoke(delegate { }, DispatcherPriority.ContextIdle);
}while(!otherthread.Join(1));
This produces a Join that doesn't block because of GUI-operations on the other thread.
这会产生一个不会因为另一个线程上的 GUI 操作而阻塞的 Join。
The main trick here is the blocking Invokewith an empty delegate (no-operation), but with a priority setting that is less than all other items in the queue. That forces the dispatcher to work through the entire queue. (The default priority is DispatcherPriority.Normal = 9, so my DispatcherPriority.ContextIdle = 3is well under.)
这里的主要技巧是Invoke使用空委托(无操作)进行阻塞,但优先级设置小于队列中的所有其他项目。这迫使调度员在整个队列中工作。(默认优先级是DispatcherPriority.Normal = 9,所以我DispatcherPriority.ContextIdle = 3的远远低于。)
The Join() call uses a 1 ms time out, and re-empties the dispatcher queue as long as the join isn't successful.
Join() 调用使用 1 ms 超时,并且只要加入不成功就会重新清空调度程序队列。
回答by Mario
I really liked @user5770690 answer. I created an extension method that guarantees continued "pumping" or processing in the dispatcher and avoids deadlocks of this kind. I changed it slightly but it works very well. I hope it helps someone else.
我真的很喜欢@user5770690 的答案。我创建了一个扩展方法来保证在调度程序中继续“抽取”或处理并避免这种死锁。我稍微改变了它,但效果很好。我希望它可以帮助别人。
public static Task PumpInvokeAsync(this Dispatcher dispatcher, Delegate action, params object[] args)
{
var completer = new TaskCompletionSource<bool>();
// exit if we don't have a valid dispatcher
if (dispatcher == null || dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished)
{
completer.TrySetResult(true);
return completer.Task;
}
var threadFinished = new ManualResetEvent(false);
ThreadPool.QueueUserWorkItem(async (o) =>
{
await dispatcher?.InvokeAsync(() =>
{
action.DynamicInvoke(o as object[]);
});
threadFinished.Set();
completer.TrySetResult(true);
}, args);
// The pumping of queued operations begins here.
do
{
// Error condition checking
if (dispatcher == null || dispatcher.HasShutdownStarted || dispatcher.HasShutdownFinished)
break;
try
{
// Force the processing of the queue by pumping a new message at lower priority
dispatcher.Invoke(() => { }, DispatcherPriority.ContextIdle);
}
catch
{
break;
}
}
while (threadFinished.WaitOne(1) == false);
threadFinished.Dispose();
threadFinished = null;
return completer.Task;
}

