为什么 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.