Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using async await still freezes GUI

I would like to handle long running operation in separate thread and return control back to GUI thread ASAP using async/await pattern as follows:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await Test();
    txtResult.Text = "Done!";
}

private Task Test()
{
    Thread.Sleep(3000);
    return Task.FromResult(0);
}

The problem is, it freezes GUI anyway for 3 seconds (it becomes unresponsive until Done! is displayed after 3 seconds). What am I doing wrong?

EDIT: I am trying to replace the following logic:

private void Button_Click(object sender, RoutedEventArgs e)
{
    var thread = new Thread(() => Test(Callback));
    thread.Start();
}

private void Callback()
{
    Dispatcher.Invoke(() =>
        txtResult.Text = "Done!");
}

private void Test(Action callback)
{
    Thread.Sleep(3000); //long running operation, not necessarily pause
    callback();
}

In actual project I have different long running logic than just Sleep, and it still freezes GUI, so replacing it with Task.Delay does not solve anything. Besides, I don't get why you should use yet another command for Sleep? How is this required by async/await design?

like image 545
eugenekr Avatar asked Mar 01 '16 13:03

eugenekr


2 Answers

You can use Task.Run or Task.Factory.StartNew to execute Test() or some long running and/or blocking operation on another thread:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(() => Test());
    txtResult.Text = "Done!";
}
like image 134
Nick Avatar answered Sep 27 '22 16:09

Nick


You're using Thread.Sleep which blocks the thread. Use Task.Delay instead, which uses a timer internally and asynchronously yields control back to the caller:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await TestAsync();
    txtResult.Text = "Done!";
}

private async Task<int> TestAsync()
{
    await Task.Delay(3000);
    return 0;
}

Edit

As you posted new code, I suggest you isolate the callback that needs to run after the log running process, and save yourself the Dispatcher.Invoke and replace it with the use of async-await which will land you on the right synchronization context for you:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(Test);
    CallBack();
}

private void Callback()
{
    txtResult.Text = "Done!"
}

private void Test()
{
    Thread.Sleep(3000); //long running operation, not necessarily pause
}
like image 36
Yuval Itzchakov Avatar answered Sep 27 '22 15:09

Yuval Itzchakov