Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF asynchronous Task<T> blocking UI

I already worked with Task type. And all was good while Task return nothing. For example:

XAML:

<Button Name="_button"
        Click="ButtonBase_OnClick">
        Click
</Button>  

CodeBehind:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    _button.IsEnabled = false;

    Task.Factory.StartNew(() =>
    {
        Thread.Sleep(5*1000);
        Dispatcher.Invoke(new Action(() => _button.IsEnabled = true));
    });
}

This works fine. But I want to Task returns some value, for example Boolean. So I need to use Task<Boolean>:

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    _button.IsEnabled = false;

    var task = Task<Boolean>.Factory.StartNew(() =>
    {
        Thread.Sleep(5*1000);
        return true;
    });

    if (task.Result)
        _button.IsEnabled = true;
}

And here we have a problem with UI blocking. UI thread is locked untill task will return result.

_button.IsEnabled = false;

So, string above is completely ignored. I am on .Net 4.0, so I cannot use async/await approach. This problem really makes me sick... Has it solution?

like image 823
monstr Avatar asked May 29 '14 13:05

monstr


1 Answers

Your main thread is blocking because the call to Task.Result waits until the Task has completed. Instead you can use Task.ContinueWith() to access the Task.Result after the Task has completed. The call to TaskScheduler.FromCurrentSynchronizationContext() causes the continuation to run on the main UI thread (so you can access _button safely).

private void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    _button.IsEnabled = false;

    Task<Boolean>.Factory.StartNew(() =>
    {
        Thread.Sleep(5*1000);
        return true;
    }).ContinueWith(t=>
    {
        if (t.Result)
            _button.IsEnabled = true;
    }, TaskScheduler.FromCurrentSynchronizationContext());        
}

Update

If you are using C# 5 you can use async/await instead.

private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
{
    _button.IsEnabled = false;

    var result = await Task.Run(() =>
    {
        Thread.Sleep(5*1000);
        return true;
    });

    _button.IsEnabled = result;      
}
like image 97
Alex Wiese Avatar answered Sep 20 '22 05:09

Alex Wiese