eugenekr
eugenekr

Reputation: 6602

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?

Upvotes: 16

Views: 21378

Answers (2)

Yuval Itzchakov
Yuval Itzchakov

Reputation: 149618

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
}

Upvotes: 3

Nick
Nick

Reputation: 324

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!";
}

Upvotes: 18

Related Questions