Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Task.Wait vs Task.RunSyncronously where task has call to WPF Dispatcher.Invoke

I have a Task that I am starting and wish to wait for completion in a WPF app. Inside this task I invoke an Action on the dispatcher.

If I use Task.Wait() it appears to hang as if the method never finished. Also, breakpoints inside the Dispatcher.Invoke are never hit.

If I use Task.RunSyncronously() it appears to work correctly and breakpoints inside the Dispatcher are hit.

Why is there a difference?

Code sample below:

public void ExampleMethod()
{
    // When doing the following:
    var task = new Task(LoadStuff);

    // This never returns:
    task.Start();
    task.Wait();

    // This version, however, does:
    task.RunSyncronously();
}

private void LoadStuff()
{
    ObservableCollection<StuffObj> stuff = Stuff.Load(arg1, true);

    DispatchHelper.RunOnDispatcher(() =>
    {
        ...
    });
}

public static class DispatchHelper
{
    public static void RunOnDispatcher(Action action)
    {
        Application.Current.Dispatcher.Invoke(action);
    }
}    
like image 441
dmck Avatar asked Nov 01 '12 20:11

dmck


1 Answers

Yes, there's a major difference. If you use RunSyncronously you just run the task in the UI thread. If you start it up in a background thread and us Wait then the code is running in a background thread and the UI thread is blocked. If the code within that task is invoking to the UI thread, and the UI thread is being blocked (by the Wait) then you've created a deadlock, and the application will remain frozen.

Note that if you used, RunSyncronously on that task from a non-UI thread, and the UI thread was being blocked by something else, you would still see the deadlock.

Now, as for what you should do, there are really two options here:

  1. The task itself doesn't actually take a long time, and it really should run in the UI thread rather than in a background thread. The UI thread won't be frozen (temporarily) for long enough to be a problem doing all of this work directly in the UI. If this is the case, you probably shouldn't even make it a Task, just put the code in a method and call the method.

  2. The task does take a long time to run, and then it updates the UI after doing that work. If that is the case then it's important that it not be RunSyncronously but started in a background thread. In order to prevent your entire application from deadlocking it will mean that you'll need to not block the UI thread through a Wait call. What you need to do if you have some code that you want to run after the task finishes, is to add a continuation to the task. In C# 4.0 this could be done by calling ContinueWith on the task, and adding in a delegate to be run. In C# 5.0+ you could instead await on the relevant task (rather than Waiting on it, which is actually a big difference) and it will automatically wire up the remainder of the method to run as a continuation for you (in effect it is syntactic sugar for an explicit ContinueWith call, but it's a very useful one).

like image 156
Servy Avatar answered Oct 18 '22 07:10

Servy