7

Consider the code:

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");
        }
    }

Of course, button_Click is run on the main thread.

My understanding is that button.Dispatcher.Thread is the main thread and the Invoke() will get processed only when the thread is not blocked. However, isn't the main thread blocked in this case? I.e. the main thread is waiting for the Dispatcher.Invoke() call to complete and Dispatcher.Invoke() is waiting for the main thread to free up. So I expect a deadlock here, but it doesn't get deadlocked.

Why?

P.S: I am aware that in this situation I don't need the Dispatcher.Invoke and that I can call the button.Content = "1234" directly. I am trying to understand why the deadlock DOES NOT happen in this case.

Vassalware
  • 30
  • 3
  • 8
Frank Adams
  • 103
  • 4
  • you don't need that dispatcher; as you say, you are on the ui thread ; just use button.Content = "1234"; – GCamel Apr 12 '17 at 08:26
  • @GCamel Yes, I am aware of that. I am trying to understand, why deadlock does not occur when i do the dispatcher.Invoke(..) – Frank Adams Apr 12 '17 at 08:27
  • so in fact, you ask for a problem, not for a solution...? WPF is so good...case seems to be normal : your button_click run and push a delegate into the processing pipe of your app – GCamel Apr 12 '17 at 08:37
  • The question makes no sense. A deadlock could only occur if at least two threads were involved. Here you have only one - the UI thread. – Clemens Apr 12 '17 at 08:55
  • @Clemens, see Nick's answer. The deadlock WILL occur had the IF condition not been there. EDIT: Just saw the other comments – Frank Adams Apr 12 '17 at 09:19
  • @FrankAdams A deadlock wouldn't occur even if that `if` doesn't evaluate to true, and the Dispatcher ends up using its alternative "Slow-Path". That's the path that is taken, even if you invoke it on the same thread, if you use a different priority other than `Send`. There still wouldn't be a deadlock, however, because the main thread is never blocked. – Vassalware Apr 12 '17 at 09:46
  • @FrankAdams You might want to read this [Wikipedia article](https://en.wikipedia.org/wiki/Deadlock). – Clemens Apr 12 '17 at 09:50

2 Answers2

10

I believe your misunderstanding may be based around the following thought process:

"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?"

If we look inside the source, we see that the callback is being called not only on the same thread, but directly* inside the Invoke method. The main thread is not being blocked.

If you look at the dispatcher's Reference Source page, you can see the following comment above an if statement within the Invoke method's implementation, with the callback being called within it:

// 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 */
}

You're calling Dispatcher.Invoke on the main thread, and the method handles that by just calling it instantly.

*Well, not directly, but the entire body of Invoke(Action) is just a call to the method that the above code is in.

Vassalware
  • 30
  • 3
  • 8
  • Thanks. If this is the case, why is it advised to call the CheckAccess() ourselves before calling the Invoke? – Frank Adams Apr 12 '17 at 08:51
  • Do you have a source that advises it? That's the first I've heard of that. – Vassalware Apr 12 '17 at 08:53
  • Even if the dispatcher action wasn't called immediately (but queued for later execution) there wouldn't be a deadlock, because there's no other thread than the UI thread. – Clemens Apr 12 '17 at 08:58
  • @Clemens I believe Frank's mindset of his misunderstanding to be something along the lines of "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?" I've added this to my answer to clarify. – Vassalware Apr 12 '17 at 09:04
2

The accepted answer perfectly explains the case when priority is Send, as asked by OP. But things become even more interesting if we specify any other priority.

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

The code above does not dead-lock either, even though it cannot call the method directly (it must process messages with higher priorities first).

In this case WPF puts our message in the Dispatcher's queue, and calls the Dispatcher.PushFrame() method. It basically spins up a nested message loop for us. The inner DispatcherFrame processes queued messages (with higher priorities) until it reaches the one that was placed in the queue by Invoke(). After that, the nested frame is stopped, and execution returns from Invoke() to the calling method.

Btw, that's also how modal dialogs work. So, it might be easier to understand by looking at it. The call to ShowDialog method does not return (remains in the call stack) until the dialog is closed. But the application stays responsive because messages are pumped by the inner DispatcherFrame.

Source code: RefereneSource.

nevermind
  • 2,300
  • 1
  • 20
  • 36