locked
Async Issue(Why blocking the UI thread)??

    Question

  • Hello all,

    I just practice Async method...

    But I just encounter a weird situation.

    Please see the below code snippet.

    button1()

    {

    var temp = testAsync();

    Debug.WriteLine("pass");

    var value= temp .Result; //Blocking forever....

    }

    async private Task<bool> testAsync()

    {

        return await Task.Run(() => {

        Debug.WriteLine("start wait one");

        Debug.WriteLine("reset");

        return true; //Code goes here without any error.....

        });

    }

    I don't know why the it will block in " var value= temp .Result; " forever.

    Could anyone help me to clarify this symptom?

    Thanks.


    Thursday, March 08, 2012 10:45 AM

Answers

  • Understanding the Problem

    When an asynchronous method resumes after an asynchronous await, it will attempt to resume in the same "context." So when your testAsync method awaits Task.Run, it captures the context before doing the await... and when the Task.Run completes, testAsync will resume in that captured context. When testAsync reaches the end of its method, then the task returned by testAsync will complete.

    Your button1 handler (I assume it's a click handler) is calling Task.Result, which waits for the asynchronous task to complete. The problem is that it is doing this blocking on the UI thread, and testAsync is trying to resume on the UI thread context. So you get a deadlock.

    Here's what's happening, step by step:

    1. button1() calls testAsync().
    2. testAsync calls Task.Run; the delegate is queued to the thread pool, and the task returned by Task.Run will be completed when the delegate completes.
    3. testAsync awaits the task returned by Task.Run. Since it's not complete yet, testAsync returns an uncompleted task to its caller. The task returned by testAsync will be completed when testAsync completes.
    4. button1 receives the uncompleted task from testAsync (storing it into "temp").
    5. button1 calls Result on the uncompleted task from testAsync. This causes button1 to block (synchronously).
    6. The thread pool delegate completes. This causes the task returned by Task.Run to complete.
    7. testAsync attempts to resume execution so that it can complete (executing its "return" statement). However, its captured context is a UI context, and the UI thread is blocked. So it waits for the UI thread to be available, while the UI thread waits for testAsync to complete.

    One Solution

    The best way (IMO) to solve this problem is to change your event handler to be asynchronous:

    private async void button1_Click(object sender, EventArgs e)
    {
      try
      {
        var temp = testAsync();
        Debug.WriteLine("pass");
        var value = await temp;
        Debug.WriteLine(value.ToString());
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
      }
    }

    However, there is a drawback to this solution: you need to think about reentrancy. e.g., what happens if the user clicks the button while the asynchronous handler is still running? Sometimes this is OK, but often you need to disable your button at the beginning of the handler and re-enable it at the end:

    private async void button1_Click(object sender, EventArgs e)
    {
      button1.Enabled = false;
      try
      {
        var temp = testAsync();
        Debug.WriteLine("pass");
        var value = await temp;
        Debug.WriteLine(value.ToString());
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
      }
      button1.Enabled = true;
    }

    Another Solution

    Another approach is to change the testAsync method so that it no longer attempts to resume on its captured context. You can do this by calling ConfigureAwait and passing false:

    private async Task<bool> testAsync()
    {
      return await Task.Run(() => {
        Debug.WriteLine("start wait one");
        Debug.WriteLine("reset");
        return true;
      }).ConfigureAwait(continueOnCapturedContext:false);
    }

    This way, testAsync will be able to complete by executing its "return" statement on a thread pool thread instead of the UI context.

    However, if you continue to use Task.Result, then any exceptions get wrapped in an AggregateException, which can be annoying:

    private void button1_Click(object sender, EventArgs e)
    {
      try
      {
        var temp = testAsync();
        Debug.WriteLine("pass");
        var value = temp.Result;
        Debug.WriteLine(value.ToString());
      }
      catch (AggregateException ex)
      {
        // This error handling code can get complex.
      }
    }

    More Information

    I have my own intro to async/await that goes into more detail than most intros. I do cover context and ConfigureAwait right in the first post.

    In general, it's a good idea to have your "utility" async methods always use ConfigureAwait, and have your "top-level" async methods (e.g., event handlers) not use ConfigureAwait.

    It's also a good idea to always use "await" and never use Result or Wait. That way you don't have to mess around with AggregateException.

    You should also watch Stephen Toub's talk The Zen of Async - one of his demos is this exact kind of deadlock.

           -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible


    • Edited by Stephen ClearyMVP Thursday, March 08, 2012 2:39 PM
    • Proposed as answer by Upsilon Chen Friday, March 09, 2012 6:55 AM
    • Marked as answer by James.WC Friday, March 09, 2012 7:22 AM
    Thursday, March 08, 2012 2:38 PM

All replies

  • Are you using the Async CTP or VS11.

    I tried it with Async CTP 3 and it works just fine.

    static void Main(string[] args)
    {
        var temp = testAsync();
        Console.WriteLine("pass");
        var value = temp.Result;
        Console.WriteLine("done");
    }
    
    async private static Task<bool> testAsync()
    {
        return await TaskEx.Run(() =>
        {
            Console.WriteLine("start wait one");
            return true;
        });
    }
    
    /*
    pass
    start wait one
    done
    */


    Paulo Morgado

    Thursday, March 08, 2012 11:58 AM
  • Ok. I think I got what you're running into.

    it's the await keyword that yields control the the calling code. The call to the Result property blocks.

    You'll have to do something like this:

    button1()
    {
        var temp = testAsync();
        Debug.WriteLine("pass");
        var value = await temp;
    }
    
    async private Task<bool> testAsync()
    {
        return await Task.Run(() => {
            Debug.WriteLine("start wait one");
            Debug.WriteLine("reset");
            return true; //Code goes here without any error.....
        });
    }
    


    Paulo Morgado

    Thursday, March 08, 2012 12:05 PM
  • Understanding the Problem

    When an asynchronous method resumes after an asynchronous await, it will attempt to resume in the same "context." So when your testAsync method awaits Task.Run, it captures the context before doing the await... and when the Task.Run completes, testAsync will resume in that captured context. When testAsync reaches the end of its method, then the task returned by testAsync will complete.

    Your button1 handler (I assume it's a click handler) is calling Task.Result, which waits for the asynchronous task to complete. The problem is that it is doing this blocking on the UI thread, and testAsync is trying to resume on the UI thread context. So you get a deadlock.

    Here's what's happening, step by step:

    1. button1() calls testAsync().
    2. testAsync calls Task.Run; the delegate is queued to the thread pool, and the task returned by Task.Run will be completed when the delegate completes.
    3. testAsync awaits the task returned by Task.Run. Since it's not complete yet, testAsync returns an uncompleted task to its caller. The task returned by testAsync will be completed when testAsync completes.
    4. button1 receives the uncompleted task from testAsync (storing it into "temp").
    5. button1 calls Result on the uncompleted task from testAsync. This causes button1 to block (synchronously).
    6. The thread pool delegate completes. This causes the task returned by Task.Run to complete.
    7. testAsync attempts to resume execution so that it can complete (executing its "return" statement). However, its captured context is a UI context, and the UI thread is blocked. So it waits for the UI thread to be available, while the UI thread waits for testAsync to complete.

    One Solution

    The best way (IMO) to solve this problem is to change your event handler to be asynchronous:

    private async void button1_Click(object sender, EventArgs e)
    {
      try
      {
        var temp = testAsync();
        Debug.WriteLine("pass");
        var value = await temp;
        Debug.WriteLine(value.ToString());
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
      }
    }

    However, there is a drawback to this solution: you need to think about reentrancy. e.g., what happens if the user clicks the button while the asynchronous handler is still running? Sometimes this is OK, but often you need to disable your button at the beginning of the handler and re-enable it at the end:

    private async void button1_Click(object sender, EventArgs e)
    {
      button1.Enabled = false;
      try
      {
        var temp = testAsync();
        Debug.WriteLine("pass");
        var value = await temp;
        Debug.WriteLine(value.ToString());
      }
      catch (Exception ex)
      {
        Debug.WriteLine(ex.Message);
      }
      button1.Enabled = true;
    }

    Another Solution

    Another approach is to change the testAsync method so that it no longer attempts to resume on its captured context. You can do this by calling ConfigureAwait and passing false:

    private async Task<bool> testAsync()
    {
      return await Task.Run(() => {
        Debug.WriteLine("start wait one");
        Debug.WriteLine("reset");
        return true;
      }).ConfigureAwait(continueOnCapturedContext:false);
    }

    This way, testAsync will be able to complete by executing its "return" statement on a thread pool thread instead of the UI context.

    However, if you continue to use Task.Result, then any exceptions get wrapped in an AggregateException, which can be annoying:

    private void button1_Click(object sender, EventArgs e)
    {
      try
      {
        var temp = testAsync();
        Debug.WriteLine("pass");
        var value = temp.Result;
        Debug.WriteLine(value.ToString());
      }
      catch (AggregateException ex)
      {
        // This error handling code can get complex.
      }
    }

    More Information

    I have my own intro to async/await that goes into more detail than most intros. I do cover context and ConfigureAwait right in the first post.

    In general, it's a good idea to have your "utility" async methods always use ConfigureAwait, and have your "top-level" async methods (e.g., event handlers) not use ConfigureAwait.

    It's also a good idea to always use "await" and never use Result or Wait. That way you don't have to mess around with AggregateException.

    You should also watch Stephen Toub's talk The Zen of Async - one of his demos is this exact kind of deadlock.

           -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible


    • Edited by Stephen ClearyMVP Thursday, March 08, 2012 2:39 PM
    • Proposed as answer by Upsilon Chen Friday, March 09, 2012 6:55 AM
    • Marked as answer by James.WC Friday, March 09, 2012 7:22 AM
    Thursday, March 08, 2012 2:38 PM
  • Dear all,

    I understand the reason why deadlock occurred...

    But as the same concept,

    If I move the temp.Result into the worker thread....

    Why deadlock never occur? 

    The code is like below..

    async private void button1_Click(object sender, EventArgs e)
            {
                await Task5();
                Debug.WriteLine("button1_Click");
            }
            async Task Task4()
            {
                Task t1 = new Task(async () =>
                {
                    Debug.WriteLine("Task Gogo");
    
                });
                t1.Start();
                await t1; // Why no deadlock??? Why only UI thread will occur deadlock?
    
                Debug.WriteLine("Outter Task4");
    
            }
            async Task Task5()
            {
                await Task.Run(async () =>
                    {
                        Task4().Wait(); //Wait and Block this thread....
                        Debug.WriteLine("Inner Task5");
                    });
                //Task4().Wait(); //If unmarked above code, deadlock happened.
                Debug.WriteLine("Outter Task5");
            }

    Thanks.


    • Edited by James.WC Friday, March 23, 2012 3:06 AM
    Friday, March 23, 2012 3:05 AM
  • Aren't you confusing deadlock with blocking?

    A deadlock is when two threads are blocking eachother.

    If you call temp.Result ont the UI thread, you are just blocking the UI thread.

    And if you use async/await there's no point in explicitly invoking Result.


    Paulo Morgado

    Friday, March 23, 2012 10:39 AM
  • The reason deadlock doesn't occur on a worker thread is because of the "context".

    As I cover in my intro post, the "context" is usually one of three things:

    1. If you're on a UI thread, then it's a UI context.
    2. If you're responding to an ASP.NET request, then it's an ASP.NET request context.
    3. Otherwise, it's usually a thread pool context.

    [The actual rules are more complex; the "context" is really the current SynchronizationContext unless it is null, in which case it is the current TaskScheduler. In most async code written today, the simple 3-point answer above is sufficient; but if you're doing complex things with async, then you need to know the actual rules.]

    So the original example (the one that causes a deadlock), the testAsync method attempts to resume on its captured context - a UI context.

    In the new example, the Task4 method runs on a thread pool thread, so it captures a thread pool context instead of a UI context. In the thread pool context, any thread pool thread may be used to continue the asynchronous method. So when t1 completes and Task4 resumes on the thread pool context, it just grabs another thread pool thread to complete the task returned by Task4.

    The central point is this: the async "context" that is captured is not for a specific thread. The UI context will synchronize back to the originating UI thread, but other contexts won't. In particular, the thread pool context won't.

         -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible

    Friday, March 23, 2012 1:21 PM