locked
async and await RRS feed

  • Question

  • Given this is the code below I'd  expect to see this output:

    ----- Enter main

    ----- Exit main

    ----- 185ms passed.

    But instead, it acts synchronously and Foo blocks MainAsync from returning. Can anyone clarify?

    class Program { static void Main(string[] args) { MainAsync().GetAwaiter().GetResult(); }

    static async Task MainAsync() { Console.WriteLine("----- Enter main"); await Foo(); Console.WriteLine("----- Exit main"); Console.ReadKey(); } public static async Task Foo() { DateTime time = DateTime.Now; HttpClient client = new HttpClient(); string urlContents = await client.GetStringAsync("http://msdn.microsoft.com"); Console.WriteLine("----- " + (DateTime.Now - time).Milliseconds + "ms passed."); } }



    • Edited by berk_canc Wednesday, December 7, 2016 6:57 AM
    Wednesday, December 7, 2016 6:57 AM

Answers

  • Your expected output is incorrect. The order would be:

    Enter Main
    ms passed
    Exit Main

    Foo is blocking MainAsync because you used await. What Foo isn't blocking is the call to MainAsync in Main. That call returns as soon as the await is hit. But since you then call GetResult it is blocked until the entire method completes.

    What happens is that your MainAsync method gets broken up. Everything up to the await becomes the original method. This method returns back the Task generated by calling Foo. Since you're using an await however the returned task gets a ContinueWith that includes everything after the await (up until the next one, if any). Hence your code will execute the "Enter" call, then it blocks until Foo completes. Foo generates the "ms passed". Once Foo completes then the final "Exit" call is made. This entire time your Main method is waiting on the original Task returned by calling Foo.

    Here's a modified example that explicitly breaks up the returns from the task waits to better clarify the ordering. But note that tasks may complete immediately so the ordering can change a little.

    class Program
    {
        static void Main ( string[] args )
        {
            Console.WriteLine("MainAsync: Calling");
            var task = MainAsync();
            Console.WriteLine("MainAsync: Waiting");
            task.Wait();
            Console.WriteLine("MainAsync: Returned");
        }
    
    
        static async Task MainAsync ()
        {
            Console.WriteLine("Foo: Calling");
            var task = Foo();
            Console.WriteLine("Foo: Waiting");
            await task;
            Console.WriteLine("Foo: Returned");
        }
    
        public static async Task Foo ()
        {            
            Console.WriteLine("Foo: Doing work");
            await Task.Delay(1000);
            Console.WriteLine("Foo: Work done");
        }
    }

    Michael Taylor
    http://www.michaeltaylorp3.net

    • Marked as answer by berk_canc Thursday, December 8, 2016 5:21 PM
    Wednesday, December 7, 2016 5:03 PM
  • "Given the code below my understanding is that calling Foo will still block the event handler "

    Yes and no. It doesn't block the handler from returning. Your handler gets broken up into (in this case) 2 separate calls. The first call returns a Task to the caller. The second call is a continuation on the first. So your handler returns after the await starts and your UI will continue to process messages. But the rest of the handler body (after the await) won't run until Foo completes. Hence it might look like your handler is blocking but it really isn't. If it had blocked then you'd likely deadlock yourself. The reason for this is the sync context.

    Whenever you use await you have the option of continuing on the same thread that originally started the await or an arbitrary thread. If you use the same thread (the default) then the continuation has to be moved back to that thread. If that thread is busy then it has to wait. Now imagine that await did block. Since it is running on your UI thread your UI freezes. The await finishes and it needs to continue on the same thread so it wants to use the UI thread but the UI thread is still in the middle of your handler so you deadlock.

    Summary, await doesn't block the caller of your method. It triggers a return with the task representing the work remaining. Any code after the await call is moved into a continuation that runs after the awaited work completes. By default all this will continue on the thread that originally triggered the work.

    "If I skip await in the event handler, Foo still executes asyncly"

    No. If you never await on something then your code executes synchronously. A task can be completed synchronously. In fact calls like Task.FromResult do just that. It is never required that a task truly be async.In fact if you put the async keyword on a method without an await then the compiler generates a warning that the method will be sync even though you marked it otherwise. It is the await keyword that triggers the generation of a task and, hence, starts the async work.

    • Marked as answer by berk_canc Thursday, December 8, 2016 5:21 PM
    Thursday, December 8, 2016 3:14 PM

All replies

  • Using await/async in a console app is always pretty confusing.

    I don't think console apps work very well with async/await and in your case I suspect the lines to Console.WriteLine are simply blocking.

    Wednesday, December 7, 2016 8:38 AM
  • Your expected output is incorrect. The order would be:

    Enter Main
    ms passed
    Exit Main

    Foo is blocking MainAsync because you used await. What Foo isn't blocking is the call to MainAsync in Main. That call returns as soon as the await is hit. But since you then call GetResult it is blocked until the entire method completes.

    What happens is that your MainAsync method gets broken up. Everything up to the await becomes the original method. This method returns back the Task generated by calling Foo. Since you're using an await however the returned task gets a ContinueWith that includes everything after the await (up until the next one, if any). Hence your code will execute the "Enter" call, then it blocks until Foo completes. Foo generates the "ms passed". Once Foo completes then the final "Exit" call is made. This entire time your Main method is waiting on the original Task returned by calling Foo.

    Here's a modified example that explicitly breaks up the returns from the task waits to better clarify the ordering. But note that tasks may complete immediately so the ordering can change a little.

    class Program
    {
        static void Main ( string[] args )
        {
            Console.WriteLine("MainAsync: Calling");
            var task = MainAsync();
            Console.WriteLine("MainAsync: Waiting");
            task.Wait();
            Console.WriteLine("MainAsync: Returned");
        }
    
    
        static async Task MainAsync ()
        {
            Console.WriteLine("Foo: Calling");
            var task = Foo();
            Console.WriteLine("Foo: Waiting");
            await task;
            Console.WriteLine("Foo: Returned");
        }
    
        public static async Task Foo ()
        {            
            Console.WriteLine("Foo: Doing work");
            await Task.Delay(1000);
            Console.WriteLine("Foo: Work done");
        }
    }

    Michael Taylor
    http://www.michaeltaylorp3.net

    • Marked as answer by berk_canc Thursday, December 8, 2016 5:21 PM
    Wednesday, December 7, 2016 5:03 PM
  • I see... It looks like console apps are don't make a very good example when studying async/await business. Given the code below my understanding is that calling Foo will still block the event handler however the work will NOT be done on the UI thread. Execution of Foo - awaited code - is given to another thread. If I skip await in the event handler, Foo still executes asyncly but this time button1_Click will return before Foo finishes execution. Is that right?

            private async void button1_Click(object sender, EventArgs e)
            {
                Console.WriteLine("button1_Click enter");
                await Foo();
                Console.WriteLine("button1_Click exit");
            }
    
            async Task Foo()
            {
                Console.WriteLine("    Foo: Enter");
                for (int i=0; i<5; i++)
                {
                    Console.WriteLine(" " + (i+1));
                    await Task.Delay(1000);
                }
                Console.WriteLine("    Foo: Exit");
            }

    Thursday, December 8, 2016 3:34 AM
  • "Given the code below my understanding is that calling Foo will still block the event handler "

    Yes and no. It doesn't block the handler from returning. Your handler gets broken up into (in this case) 2 separate calls. The first call returns a Task to the caller. The second call is a continuation on the first. So your handler returns after the await starts and your UI will continue to process messages. But the rest of the handler body (after the await) won't run until Foo completes. Hence it might look like your handler is blocking but it really isn't. If it had blocked then you'd likely deadlock yourself. The reason for this is the sync context.

    Whenever you use await you have the option of continuing on the same thread that originally started the await or an arbitrary thread. If you use the same thread (the default) then the continuation has to be moved back to that thread. If that thread is busy then it has to wait. Now imagine that await did block. Since it is running on your UI thread your UI freezes. The await finishes and it needs to continue on the same thread so it wants to use the UI thread but the UI thread is still in the middle of your handler so you deadlock.

    Summary, await doesn't block the caller of your method. It triggers a return with the task representing the work remaining. Any code after the await call is moved into a continuation that runs after the awaited work completes. By default all this will continue on the thread that originally triggered the work.

    "If I skip await in the event handler, Foo still executes asyncly"

    No. If you never await on something then your code executes synchronously. A task can be completed synchronously. In fact calls like Task.FromResult do just that. It is never required that a task truly be async.In fact if you put the async keyword on a method without an await then the compiler generates a warning that the method will be sync even though you marked it otherwise. It is the await keyword that triggers the generation of a task and, hence, starts the async work.

    • Marked as answer by berk_canc Thursday, December 8, 2016 5:21 PM
    Thursday, December 8, 2016 3:14 PM