1

I have a WPF program with a button which creates and displays some data which is databound to a grid. The process of creating the data is very slow and CPU bound hence I offload it to a task. I want to display the first chunk of data as soon as its ready, then display the second chunk.

Here are 3 implementations which all work and keep the UI responsive.

await Dispatcher.InvokeAsync, Dispatcher.Invoke and Dispatcher.Invoke (inside the Task.Run). Which of these is going to avoid blocking a thread on the threadpool that could otherwise be doing work, and which is the least likely to result in a deadlock if someone had blocked the UI thread elsewhere in the program?

public ObservableCollection<BigObject> DataBoundList {get;set;}
public ObservableCollection<BigObject> DataBoundList2 {get;set;}

//Click handler from WPF UI button
public async void ClickHandlerCommand()
{
    List<BigObject> items1 = null;
    List<BigObject> items2 = null;
    
    //On UI Thread
    await Task.Run(() =>
    {
        //On thread X from threadpool
        items1 = SlowCPUBoundMethod1();
        
    }).ConfigureAwait(false);

    Dispatcher.Invoke(() => 
    { 
        //On UI Thread
        DataBoundList = new ObservableCollection<BigObject>(items1);
        RaisePropertyChanged(nameof(DataBoundList));
    });
    
    //On thread X from threadpool
    await Task.Run(() =>
    {
        //On thread Y from threadpool
        items2 = SlowCPUBoundMethod2();
        
    }).ConfigureAwait(false);
    
    //On thread Y from threadpool

    Dispatcher.Invoke(() => 
    { 
        //On UI Thread
        DataBoundList2 = new ObservableCollection<BigObject>(items2);
        RaisePropertyChanged(nameof(DataBoundList2));
    });
    //On thread Y from threadpool
    //5x context switches
}

The implementation above puts the dispatcher call outside the Task.Run. This will likely cause two threads to be spun up. If another thread someone in the program had blocked the UI thread then I think the Dispatcher.Invoke call would possibly deadlock?

public async void ClickHandlerCommand2()
{
    List<BigObject> items = null;
    List<BigObject> items2 = null;

    //On UI Thread 

    await Task.Run(() =>
    {
        //On thread X from threadpool

        items1 = SlowCPUBoundMethod1();
        
        Dispatcher.Invoke(() => 
        { 
            //On UI thread
            DataBoundList = new ObservableCollection<BigObject>(items1);
            RaisePropertyChanged(nameof(DataBoundList));
        });

        //On thread X from threadpool
        items2 = SlowCPUBoundMethod2();
        
        Dispatcher.Invoke(() => 
        { 
            //On UI thread
            DataBoundList2 = new ObservableCollection<BigObject>(items2);
            RaisePropertyChanged(nameof(DataBoundList2));
        });

        //On thread X from threadpool
        
    }).ConfigureAwait(false);

    //On thread X from threadpool
    //5x context switches
}

The implementation above will have a single thread, however if another thread someone in the program had blocked the UI thread then I think the Dispatcher.Invoke call would possibly deadlock?

public async void ClickHandlerCommand3()
{
    List<BigObject> items1 = null;
    List<BigObject> items2 = null;

    //On UI Thread

    await Task.Run(() =>
    {
        //On thread X from threadpool
        items1 = SlowCPUBoundMethod1();
        
    }).ConfigureAwait(false);

    //On thread X from threadpool

    await Dispatcher.InvokeAsync(() => 
    { 
        //On UI Thread
        DataBoundList = new ObservableCollection<BigObject>(items1);
        RaisePropertyChanged(nameof(DataBoundList));
    });
    
       
    //On thread X from threadpool
    items2 = SlowCPUBoundMethod2();

    await Dispatcher.InvokeAsync(() => 
    { 
        //On UI Thread
        DataBoundList2 = new ObservableCollection<BigObject>(items2);
        RaisePropertyChanged(nameof(DataBoundList2));
    });

    //On thread X from threadpool
    //5x context switches
}

This should result in only 1 task being spun up and I believe reduce the risk of a deadlock if someone somewhere else has blocked the UI thread. I think this is the best implementation?

Can someone categorically say which is the correct implementation? I believe the third example using await Dispatcher.InvokeAsync is the correct one but I'm not completely sure.

rollsch
  • 2,518
  • 4
  • 39
  • 65
  • If the current task is running on a thread pool thread then `ConfigureAwait` has no effect (unlike when it is running on the UI thread). There is no guarantee that it will continue on the same thread after the await. – JosephDaSilva Jun 20 '21 at 03:36
  • What's the intention behind the `ConfigureAwait(false)`? This configuration is intended for library code, and using it in application code makes your code less reliable, and it's intentions more obscure. There is a better way to offload work to a `ThreadPool` thread, the `Task.Run` method, and you are already using it. What's the point of complicating matters with the `ConfigureAwait` stuff? – Theodor Zoulias Jun 20 '21 at 03:52
  • @TheodorZoulias ConfigureAwait makes it explicit what I am doing and what I expect to happen. The default is true which means it will always context switch back to the capture context. If you know you don't want this to happen you can pass in false and make it save a context switch with the resultant code running on the same thread as the task.Run started. I would argue that "application code makes your code less reliable, and it's intentions more obscure" the complete opposite is true, it tells you exactly what the intentions are. – rollsch Jun 20 '21 at 04:20
  • @JosephDaSilva If you pass in ConfigureAwait(false) I thought it did guarantee it would continue on the same thread as the Task.Run/threadpool used? Isn't that the whole point of it? Or at least it would guarantee it wouldn't return on the UI thread? – rollsch Jun 20 '21 at 04:21
  • Either way this doesn't really answer the question of awaiting Dispatcher.InvokeAsync vs just calling Dispatcher.Invoke – rollsch Jun 20 '21 at 04:23
  • @rolls The reason `ConfigureAwait(true)` (or not calling ConfigureAwait) causes the task to continue on the UI thread is because WPF sets up a SynchronizationContext for it which implements this behaviour. Tasks running on threadpool threads use the default SynchronizationContext (unless you explicitly set a different one), which runs continuations on *a threadpool thread*, but not necessarily the same one as where the await left off. Continuing on the same thread would require blocking it, or implementing an event loop like what the UI thread has. – JosephDaSilva Jun 20 '21 at 04:31
  • @JosephDaSilva I think I understand. So you are saying that if you were already on the thread pool and call ConfigureAwait(true) it will mean you might return on a different thread (but definitely not the UI thread). If there were no other threads running then you'd return on the same thread. This makes sense. If I wanted to ensure it returned on the same thread (to limit context switching) then I'd need to implement my own synchronisation context backed by a single thread. – rollsch Jun 20 '21 at 04:59
  • So if I understand correctly, the intention of the `ConfigureAwait(false)` is to make the code that follows to run on the same thread with the previously completed `Task.Run`. Why not put this code inside the `Task.Run` then? Wouldn't this be simpler and more expressive? Regarding the reliability issue: the problem is that it's theoretically possible for the `Task.Run` task to be already completed at the time it is `await`ed, in which case the code that follows will continue running synchronously on the UI thread, which is against your intentions. – Theodor Zoulias Jun 20 '21 at 05:10
  • Btw if you are a fan of controlling the current context imperatively, you may like noseratio's [`TaskSchedulerExtensions`](https://gist.github.com/noseratio/5d2d5f2a0cbb71b7880ce731c3958e62). It allows to switch reliably to the `ThreadPool` context like this: `await TaskScheduler.Default.SwitchTo();` – Theodor Zoulias Jun 20 '21 at 05:10
  • @TheodorZoulias that looks perfect for what I am doing. We have a UI where basically every single operation is very CPU bound and slow, but there is a lot of UI updates required. So we are continually doing await Task.Run then Dispatcher.Invoke etc. I am trying to find the optimal way to achieve this so we don't block the UI but we also don't spawn unnecessary thread and/or risk deadlocks. – rollsch Jun 20 '21 at 05:16
  • 1
    Yeap, it looks tempting, but you may want to read this question on order to understand why it may not be a good idea: [Why was “SwitchTo” removed from Async CTP / Release?](https://stackoverflow.com/questions/15363413/why-was-switchto-removed-from-async-ctp-release) But if it makes sense for your application, you could certainly consider following that path. – Theodor Zoulias Jun 20 '21 at 05:20
  • @TheodorZoulias Is adding a single await Task.Run(() => {int test = 1;}).ConfigureAwait(false); and then putting slow code outside the task.Run effectively the same as calling TaskScheduler.Default.SwitchTo() ? – rollsch Jun 20 '21 at 05:26
  • 1
    Yes, it's the same, but it's not 100% reliable. It depends on the `Task.Run` task being non-completed at the `await` point, which AFAIK it's not guaranteed. – Theodor Zoulias Jun 20 '21 at 05:29
  • @rolls, Windows 10 Desktop? If yes, then your example is some kind of nonsense. There is no need to go to the UI thread to assign a value to the ViewModel property and raise the PropertyChanged. This can be done from any stream. The transition to the UI thread is needed for CollectionChanged and CanExecuteChanged. – EldHasp Jun 20 '21 at 06:25
  • Also, in the code examples in the question, it makes no sense to use ObservableCollection. A simple IEnumerable is enough. – EldHasp Jun 20 '21 at 06:44
  • My examples are because my program requires collection changed and many other UI only thread updates. I could make them more complicated to show that if you want but I must execute the changes on the UI thread – rollsch Jun 20 '21 at 10:38

2 Answers2

3

Both Dispatcher.Invoke, and InvokeAsync execute the delegate on the dispatcher's thread. The former does this synchronously, and will block the calling thread until the delegate finishes execution; the latter doesn't block the calling thread.

Both methods enque the delegate somewhere in the dispatcher's processing queue, based on the DispatcherPriority param (unless you use send priority, then Dispatcher.Invoke may bypass the queue and invoke the delegate immediately). It follows then, that the lower the priority, the longer the calling thread may be blocked while waiting for it to complete (if you use Dispatcher.Invoke).

The third approach, Task.Run(() => Dispatcher.Invoke()), doesn't block the original calling thread, but it does block the thread on which the task is running (presumably a thread pool thread).

Dispatcher.InvokeAsync is the best approach for your use-case, it was designed for this exact purpose.

John Colvin
  • 304
  • 3
  • 6
2

This is not an answer to the question asked, which is about the difference between Dispatcher.Invoke and Dispatcher.InvokeAsync. I would like to share my personal preference between these two methods, which is to use neither. They are both ugly, cumbersome, and for the most part redundant. The Task.Run is sufficient for offloading work to the ThreadPool, and then awaiting the created Task<TResult> is enough for grabbing the result of the computation, and using it on the UI thread:

public async void ClickHandlerCommand()
{
    var items = await Task.Run(() => SlowCPUBoundMethod1());
    DataBoundList = new ObservableCollection<BigObject>(items1);
    RaisePropertyChanged(nameof(DataBoundList));

    var items2 = await Task.Run(() => SlowCPUBoundMethod2());
    DataBoundList2 = new ObservableCollection<BigObject>(items2);
    RaisePropertyChanged(nameof(DataBoundList2));
}

In case more granular communication is needed between the UI and the background thread, one or more IProgress<T> objects can be used for establishing this communication. The background thread is passed an IProgress<T> object and uses it to report progress in an abstract way, and the UI thread receives these progress notifications and uses them for updating the UI. An example of using the IProgress<T> interface can be found here. This is a good reading too: Async in 4.5: Enabling Progress and Cancellation in Async APIs.

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • But you have to execute the progress update on the UI thread. Raising collection changed from a background thread is specifically said not to be done in the MS documentation. You must use dispatcher invoke. Some controls (devexpress) raise an exception if you don't and take the whole application down. Observable collection must also be newed on the UI thread, that is also said in the MS documentation. How is your function going to work if it was called from a thread that was not the ui thread? – rollsch Jun 20 '21 at 10:34
  • @rolls the [`Progress`](https://learn.microsoft.com/en-us/dotnet/api/system.progress-1) class captures the current `SynchronizationContext` when it is instantiated, and ensures that the notification callbacks will be invoked on this context. So you can safely interact with UI components from inside the callback delegate. – Theodor Zoulias Jun 20 '21 at 11:02
  • Yep I hear what you are saying, but its still doing a dispatcher.invoke, its just abstracted away from your code. Most of our issues stem from RaiseCollectionChanged events which have to be on the UI thread and these don't really make sense to do from an IProgress etc. I get that in many cases PropertyChanged can be done from a background thread, but in our case with the controls we use it is not safe to do so hence the question I've asked. – rollsch Jun 21 '21 at 01:50
  • Here is some discussion of some of the scenarios which we have. Eg we need an observablecollection for an ItemsControl which we then raise collectionchanged on. Another is a TreeList where we update ~4000 checkboxes and backgroundcolours during some expensive operations. https://supportcenter.devexpress.com/ticket/details/t661389/is-it-necessary-to-raise-the-inotifypropertychanged-in-a-gui-thread/ – rollsch Jun 21 '21 at 01:51
  • @rolls yeap, the `Progress` is an abstraction over `SynchronizationContext.Post`, which abstracts `BeginInvoke`. It's a nice abstraction because it allows to write the background processing logic without tying it with the presentation logic. I suggested it for the cases that the required back-front communication is granular. If the communication is chunky, you can just offload anything expensive with `Task.Run`, and allow the context to switch naturally to the UI thread between consecutive `Task.Run`s, as shown in the example of this answer. Is this approach not viable for your application? – Theodor Zoulias Jun 21 '21 at 03:32
  • 1
    @rolls to be clear I am aware that all UI controls, native and third-party, should be accessed exclusively from the UI thread, and nowhere I am suggesting to circumvent this rule. – Theodor Zoulias Jun 21 '21 at 03:42