Friday, September 6, 2013

Other ways to update the UI from another thread

When programming in WPF (or any other desktop UI framework) you don't want to block the UI thread when running long tasks. Apart from what we saw in the previous post, there are other ways to do it if you are using previous versions of .NET.
For example, if you are using .NET 4 you can use the TPL:

Working=Visibility.Visible;
var tasca=Task.Run(()=>LongRunningTask());
tasca.ContinueWith(t=>
    {
        var exception=t.Exception;
        LogAndShowException(exception);
    },CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted,TaskScheduler.FromCurrentSynchronizationContext());
tasca.ContinueWith(t=>
    {
        var result=t.Result;
        UpdateUI(result);
    },CancellationToken.None, TaskContinuationOptions.OnlyOnRanToCompletion,TaskScheduler.FromCurrentSynchronizationContext());
tasca.ContinueWith(t=>
    {
        Working=Visibility.Collapsed;
    },TaskScheduler.FromCurrentSychronizationContext());

Here we make two continuation tasks: one to deal with faulted states and the other to deal with the correct state. The trick to run these tasks on the UI thread is the TaskScheduler.FromCurrentSynchronizationContext(). The last continuation task runs always to set the Working property to Collapsed.

Another option for older versions of .NET is to use a BackgroundWorker:

BackgroundWorker bw=new BackgroundWorker();
bw.DoWork+=bw_DoWork;
bw.OnCompleted+=bw_RunWorkerCompleted;
Working=Visibility.Visible;
bw.RunWorkerAsync();

private void bw_DoWork(object sender, DoWorkEventArgs e)
{
    LongRunningTask();
}

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if(e.Error!=null){
        LogAndShowException(e.Error);
    }
    else{
        var.result=e.Result;
        UpdateUI(result);
    }
    Working=Visibility.Collapsed;
}

This are some ways to run tasks in the background when using WPF. But the easiest way is to use async/await if you can afford to use .NET 4.5.