-1

I'm trying to understand await and async.

It works very well. But now I have a deadlock.

I've called ConfigureAwait with false, like in this article, but my code is still blocking.

Here's a little snippet of my code:

private void button1_Click(object sender, EventArgs e)
{
    var result = HeavyWorkAsync().Result;
    richTextBox1.AppendText(result);
}

private string HeavyWork()
{
    for (var index = 0; index < 1000; index++)
    {
        Task.Delay(10).Wait();
    }

    return "finished";
}

private async Task<string> HeavyWorkAsync()
{
    var task = await Task.Factory.StartNew<string>(HeavyWork).ConfigureAwait(false);
    return task;
}
CarenRose
  • 1,266
  • 1
  • 12
  • 24
Marcel Hoffmann
  • 973
  • 1
  • 8
  • 15
  • 4
    can you explain why you dont want to do `await HeavyWorkAsync()` ? – thumbmunkeys Mar 24 '15 at 11:53
  • In my opinion, it's ugly. Async in the click handler of a button? – Marcel Hoffmann Mar 24 '15 at 11:54
  • 3
    It is the recommended way to handle this scenario and on top of that it's not blocking or deadlocking :) – thumbmunkeys Mar 24 '15 at 11:56
  • On top? What is this doing else, if this is no deadlock, like in the article? – Marcel Hoffmann Mar 24 '15 at 11:57
  • 1
    `async void` is legal precisely to handle this kind of case. Yes, `async void` is ugly and should be avoided in every signature that is not a subscribtion to an event, but we have no choice because the UI framework we use does not support proper async callbacks. – Falanwe Mar 24 '15 at 12:01
  • @MarcelHoffmann your code wouldn't deadlock, the code in the article works in that respect. Although it would still block the UI for 10 seconds, due to handling `var result = HeavyWorkAsync().Result;` on the UI thread. – thumbmunkeys Mar 24 '15 at 12:06

2 Answers2

4

What's blocking is not the task itself, it's the call to Result. A Task represents an asynchronous operation, but calling its Result property, or calling Wait() will block the current thread until the method returns. And in a lot of cases, it will cause a deadlock because the task is not able to complete with it's calling thread blocked!

To prevent that, chain the tasks asynchronously, using async and await

private async void button1_Click(object sender, EventArgs e)
{
    var result = await HeavyWorkAsync(); // <=== await
    richTextBox1.AppendText(result);
}

Also, Task.Delay(10).Wait(); completely defeats the prupose of using tasks in the first place: that will block the current thread. If that's really what you want to do (and it's pretty unlikely), call Thread.Sleep(10);instead, it will make your intent much clearer, and you will have less hoops to jump through. Or better, use await Task.Delay(10);in an async method.

About ConfigureAwait

What exactly does ConfigureAwait(false) do?

It removes the obligation for the continuation of the task to run in the same context as the caller of the task. In most cases that means that the continuation is no longer guaranteed to run on the same context. So if I have a method thad does Foo(), waits a little then Bar() like this one:

async Task DoStufAsync()
{
    Foo();
    await Task.Delay(10);
    Bar(); // run in the same context as Foo()
}

I'm guaranteed Bar will run in the same context. If I had ConfigureAwait(false), it's no longer the case

async Task DoStufAsync()
{
    Foo();
    await Task.Delay(10).ConfigureAwait(false);
    Bar(); // can run on another thread as Foo()
}

When you're using ConfigureAwait(false), you tell your program you dont mind about the context. It can solve some deadlocking problems, but isn't usually the right solution. The right solution is most likely never to wait for tasks in a blocking way, and being asynchronous all the way.

Community
  • 1
  • 1
Falanwe
  • 4,636
  • 22
  • 37
  • 1
    Note that the article / OP is attempting to use `ConfigureAwait` to avoid having to make the click handler `async` in the first place. – James Thorpe Mar 24 '15 at 11:38
  • 1
    The problem is `ConfigureAwait` doesn't do that: the UI thread will become unresponsive because of the call to `Result` regardless of the internal workings of the methods. The best he can hope for is have a huge ugly freeze that will eventually finish instead of an infinite deadlock. – Falanwe Mar 24 '15 at 11:44
  • Could you explain that in your answer to complete it then since it's the main focus of the question - I'm fairly new to async stuff myself, but it seems that the article is implying that it's possible to use it in this way – James Thorpe Mar 24 '15 at 11:46
  • @JamesThorpe : I certainly can, and will right now! – Falanwe Mar 24 '15 at 11:48
  • Ok, I think I've understand it. If I use this code in a non-ui thread, then should work my code, right? – Marcel Hoffmann Mar 24 '15 at 11:56
  • @MarcelHoffmann : yes, it will work, but you will still be blocking a thread needlessly. Threads are a precious resource, don't waste them. – Falanwe Mar 24 '15 at 11:58
  • Okay, thanks for you're answer. The better way is for example calling an event, right? This solution is non blocking. – Marcel Hoffmann Mar 24 '15 at 11:59
  • 2
    `await` does not resume on the same thread; it resumes on the same *context*. A UI context has a one-to-one relationship with a thread, but other contexts (e.g., ASP.NET, thread pool, etc) do not. – Stephen Cleary Mar 24 '15 at 12:14
1

To expand upon Falanwe's answer, you should check out Stephen Cleary's blog post. Based off of the code I'm assuming that you are using a Windows Forms application, so a call to Task.Result will execute the task on the UI context, which in turn blocks the UI thread.

mjk5182
  • 63
  • 6