为什么 WPF 的 Dispatcher.Invoke 在主线程上 运行 时不会导致死锁?
Why does WPF's Dispatcher.Invoke not cause a deadlock when run on the main thread?
考虑代码:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
//System.Threading.Thread.CurrentThread = button.Dispatcher.Thread
button.Dispatcher.Invoke(() => button.Content = "1234");
}
}
当然,button_Click
在主线程上运行。
我的理解是button.Dispatcher.Thread
是主线程,Invoke()
只有在线程不被阻塞时才会被处理。但是,这种情况下主线程不是被阻塞了吗? IE。主线程正在等待 Dispatcher.Invoke()
调用完成,而 Dispatcher.Invoke()
正在等待主线程释放。所以我预计这里会出现死锁,但不会死锁。
为什么?
P.S:我知道在这种情况下我不需要 Dispatcher.Invoke
并且我可以直接调用 button.Content = "1234"
。我试图理解为什么在这种情况下不会发生死锁。
我相信您的误解可能是基于以下思考过程:
"Well, Invoke blocks the calling thread until the action is completed. How can it perform the action on the thread if the thread is blocked?"
如果我们查看源代码,我们会发现 回调不仅在同一个线程上被调用,而且直接*在 Invoke 方法中被调用。 主线程不是被屏蔽了。
如果您查看调度程序的 Reference Source 页面,您可以在 Invoke
方法实现中的 if
语句上方看到以下注释,其中调用了回调:
// Fast-Path: if on the same thread, and invoking at Send priority,
// and the cancellation token is not already canceled, then just
// call the callback directly.
if(!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{
/* snipped */
callback();
/* snipped */
}
您在主线程上调用 Dispatcher.Invoke
,该方法通过立即调用它来处理。
*嗯,不是直接,而是Invoke(Action)
的整个主体只是对上面代码所在的方法的调用。
接受的答案完美地解释了 OP 要求的优先级为 Send
的情况。但是如果我们指定任何其他优先级,事情会变得更加有趣。
button.Dispatcher.Invoke(() => button.Content = "1234", DispatcherPriority.Input);
上面的代码也不会dead-lock,即使它不能直接调用该方法(它必须首先处理具有更高优先级的消息)。
在这种情况下,WPF 将我们的消息放入 Dispatcher 的队列中,并调用 Dispatcher.PushFrame()
方法。它基本上为我们启动了一个嵌套的消息循环。内部 DispatcherFrame
处理排队的消息(具有更高优先级),直到它到达由 Invoke()
放置在队列中的消息。之后,嵌套框架停止,从Invoke()
执行returns到调用方法。
顺便说一句,模态对话框也是这样工作的。因此,通过查看它可能更容易理解。在对话框关闭之前,对 ShowDialog 方法的调用不会 return (保留在调用堆栈中)。但是应用程序保持响应,因为消息是由内部 DispatcherFrame
.
发送的
源代码:RefereneSource.
考虑代码:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void button_Click(object sender, RoutedEventArgs e)
{
//System.Threading.Thread.CurrentThread = button.Dispatcher.Thread
button.Dispatcher.Invoke(() => button.Content = "1234");
}
}
当然,button_Click
在主线程上运行。
我的理解是button.Dispatcher.Thread
是主线程,Invoke()
只有在线程不被阻塞时才会被处理。但是,这种情况下主线程不是被阻塞了吗? IE。主线程正在等待 Dispatcher.Invoke()
调用完成,而 Dispatcher.Invoke()
正在等待主线程释放。所以我预计这里会出现死锁,但不会死锁。
为什么?
P.S:我知道在这种情况下我不需要 Dispatcher.Invoke
并且我可以直接调用 button.Content = "1234"
。我试图理解为什么在这种情况下不会发生死锁。
我相信您的误解可能是基于以下思考过程:
"Well, Invoke blocks the calling thread until the action is completed. How can it perform the action on the thread if the thread is blocked?"
如果我们查看源代码,我们会发现 回调不仅在同一个线程上被调用,而且直接*在 Invoke 方法中被调用。 主线程不是被屏蔽了。
如果您查看调度程序的 Reference Source 页面,您可以在 Invoke
方法实现中的 if
语句上方看到以下注释,其中调用了回调:
// Fast-Path: if on the same thread, and invoking at Send priority,
// and the cancellation token is not already canceled, then just
// call the callback directly.
if(!cancellationToken.IsCancellationRequested && priority == DispatcherPriority.Send && CheckAccess())
{
/* snipped */
callback();
/* snipped */
}
您在主线程上调用 Dispatcher.Invoke
,该方法通过立即调用它来处理。
*嗯,不是直接,而是Invoke(Action)
的整个主体只是对上面代码所在的方法的调用。
接受的答案完美地解释了 OP 要求的优先级为 Send
的情况。但是如果我们指定任何其他优先级,事情会变得更加有趣。
button.Dispatcher.Invoke(() => button.Content = "1234", DispatcherPriority.Input);
上面的代码也不会dead-lock,即使它不能直接调用该方法(它必须首先处理具有更高优先级的消息)。
在这种情况下,WPF 将我们的消息放入 Dispatcher 的队列中,并调用 Dispatcher.PushFrame()
方法。它基本上为我们启动了一个嵌套的消息循环。内部 DispatcherFrame
处理排队的消息(具有更高优先级),直到它到达由 Invoke()
放置在队列中的消息。之后,嵌套框架停止,从Invoke()
执行returns到调用方法。
顺便说一句,模态对话框也是这样工作的。因此,通过查看它可能更容易理解。在对话框关闭之前,对 ShowDialog 方法的调用不会 return (保留在调用堆栈中)。但是应用程序保持响应,因为消息是由内部 DispatcherFrame
.
源代码:RefereneSource.