15

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?

Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
eugenekr
  • 6,260
  • 3
  • 21
  • 26
  • Possible duplicate of [C# (async / await) loop in task block my main thread?](http://stackoverflow.com/questions/24310239/c-sharp-async-await-loop-in-task-block-my-main-thread) – Valentin Mar 01 '16 at 13:22
  • 4
    async doesn't cause your code to run on a separate thread. It allows the current thread to not be blocked when used on code that either does create a task to run on another thread or does IO bound tasks that you want to await. Your method however does neither of those things. – juharr Mar 01 '16 at 13:31

2 Answers2

18

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!";
}
Nick
  • 324
  • 1
  • 2
  • 4
  • 4
    Sorry, why it should be await Task.Run(() => Test()); and not just await Test() ? – eugenekr Mar 02 '16 at 08:43
  • 4
    Test() returns a synchronous Task and simply awaiting it will not start the Task on a separate thread. The [documentation](https://msdn.microsoft.com/en-us/library/hh191443.aspx?f=255&MSPPError=-2147217396), explains this far better than I can: "The async and await keywords don't cause additional threads to be created." and "You can use Task.Run to move CPU-bound work to a background thread". – Nick Mar 02 '16 at 19:07
  • Task.Run is part of Task Parallel Library. And it plays nicely with await in that case. There are plenty of ways in c# to do something in background. I recommend reading this quick walkthrough for better understanding https://markheath.net/post/starting-threads-in-dotnet – Deepscorn Apr 17 '20 at 16:18
  • This answer helped me thank you for the question & answer :-) – Slamit Oct 22 '21 at 19:42
3

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
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • 1
    Thanks for reply, in actual project I have long running logic instead of Sleep, please see edited question. – eugenekr Mar 02 '16 at 08:40
  • OK, so await Task.Run(Test) runs asynchronously while await Task runs synchronously? What is the point of await Task() comparing to calling Task() directly? – eugenekr Mar 02 '16 at 15:37
  • @EugeneKr `await` is simply a keyword used to flag a simple hint to the compiler. The asynchrony isn't done by `await`, it's done by the underlying method which is being awaited. What this means is, for example, if you're doing async IO with `HttpClient` for example, which exposes naturally async methods, then when you `await` one of it's methods, control will yield back to the calling method. When you use `Task.Run`, you're explicitly running a delegate on a thread-pool thread, which is different than using naturally async API's, which require no extra threads. – Yuval Itzchakov Mar 02 '16 at 15:41
  • 1
    I'm starting to understand this... I guess. Still failing to see where it is useful. Although it would be much easier if I had a piece of sample code in front of me that uses await Test(), similar to the ones I provided if possible. Hopefully you still have a bit of patience to do this :) I can hardly see how example with await Task.Delay is useful. – eugenekr Mar 02 '16 at 16:18
  • @EugeneKr Im not really sure what you mean. `await Test()`, as per your example, does nothing asynchronously. It has a blocking part, which is simulated using `Thread.Sleep`, and uses the utility `Task.FromResult` which again does nothing asynchronous, it simply creates a completed `Task` object using `TaskCompletionSource`. Awaiting that will not do anything but block the calling thread. – Yuval Itzchakov Mar 02 '16 at 16:28
  • Not necessarily with my examples, I am just looking for a simple example of useful await usage, without Task.Run. I've researched a couple of articles but still don't get what async/await useful for. Maybe better to create new question "Simple example of async/await usage" ? – eugenekr Mar 02 '16 at 16:34