为什么 WPF 的 Dispatcher.Invoke 在主线程上运行时不会导致死锁?



考虑代码:

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()才会得到处理。但是,在这种情况下,主线程没有被阻止吗?即主线程正在等待Dispatcher.Invoke()调用完成,Dispatcher.Invoke()正在等待主线程释放。所以我预计这里会出现僵局,但它不会陷入僵局。

为什么?

PS:我知道在这种情况下我不需要Dispatcher.Invoke,我可以直接打电话给button.Content = "1234"。我试图理解为什么在这种情况下不会发生死锁。

我相信你的误解可能是基于以下思维过程:

"好吧,调用会阻止调用线程,直到操作完成。如果线程被阻塞,它如何在线程上执行操作?

如果我们查看源代码内部,我们会看到回调不仅在同一线程上被调用,而且直接在 Invoke 方法中*调用。主线程未被阻塞。

如果您查看调度程序的"引用源"页,您可以在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)体只是对上述代码所在的方法的调用。

接受的答案完美地解释了优先级Send的情况,正如OP所问的那样。但是,如果我们指定任何其他优先级,事情就会变得更加有趣。

button.Dispatcher.Invoke(() => button.Content = "1234", DispatcherPriority.Input);

上面的代码也不会死锁,即使它不能直接调用该方法(它必须首先处理优先级更高的消息)。

在这种情况下,WPF 将我们的消息放入调度程序的队列中,并调用Dispatcher.PushFrame()方法。它基本上为我们启动了一个嵌套的消息循环。内部DispatcherFrame处理排队的消息(具有更高的优先级),直到它到达由Invoke()放入队列的消息。之后,嵌套帧停止,执行从Invoke()返回到调用方法。

顺便说一句,这也是模态对话框的工作方式。因此,通过查看它可能更容易理解。在对话框关闭之前,对 ShowDialog 方法的调用不会返回(保留在调用堆栈中)。但是应用程序保持响应,因为消息是由内部DispatcherFrame泵送的。

源代码:引用源。

最新更新