rollsch
rollsch

Reputation: 2780

await Dispatcher.InvokeAsync vs Dispatcher.Invoke

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.

Upvotes: 2

Views: 5330

Answers (2)

Theodor Zoulias
Theodor Zoulias

Reputation: 43846

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.

Upvotes: 3

John Colvin
John Colvin

Reputation: 334

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.

Upvotes: 5

Related Questions