locked
Async / await - I just don't get it RRS feed

  • Question

  • I've been programming in C# for 10 years.  Have done many years of multi-threaded programming with threads, thread pools and most recently with Task<T>.  I fully understand tasks, ContinueWith(), cancellation, etc.  Today, I decided to try and understand what "async" is all about.  I read: Asynchronous Programming  and tried out a program:

    	class Program {
    		static void Main(string[] args) {
    
    			try {
    				AsyncTester tester = new AsyncTester();
    				tester.Test1();
    			}
    			catch (Exception e) {
    				Console.WriteLine("Exception:{0}\n{1}", e.Message, e.StackTrace);
    			}
    			finally {
    				Console.WriteLine("<CR> to exit...");
    				Console.ReadLine();
    			}
    		}
    	}
    
    ...
    
    	public class AsyncTester {
    
    		public async void Test1() {
    			float seconds = await WaitForTimeout(3000);
    			Console.WriteLine("{0} seconds", seconds);
    		}
    
    		public async Task<float> WaitForTimeout(int milliseconds) {
    			Stopwatch timer = new Stopwatch();
    			timer.Restart();
    			Task delay1 = Delay(milliseconds);
    			await delay1;
    			timer.Stop();
    			return (float) timer.Elapsed.TotalSeconds;
    		}
    
    		private Task Delay(int milliseconds) {
    			return new Task(() => Thread.Sleep(milliseconds));
    		}
    	}
    

    I expected the Test1() method to delay for 3 seconds and then print the time it actually took.  Instead, it blows right through Test1() and returns.  I thought the "await" keyword would cause it to wait for the result.  If I take out the "async" keyword in front of Test1(), it won't compile.

    Again, I just don't understand why I would ever use async/await.  The documentation says that this is a "simplified" approach.  Until someone can explain to me what this does, I'll stick to Tasks.

    Tuesday, December 9, 2014 6:59 PM

Answers

  • The "What Happens in an Async Method" section on the link explains it pretty well: http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx

    When the program reaches the await expression in your Test1() method, the control will return to the caller (Main) and the remainder of the Test1() method will be executed once the WaitForTimeout method has completed. During the time it takes for the WaitForTimeout to execute, the Test1() method is suspended and control returns to the method's caller.

    There is however a bug in your program. The Task returned from the Delay method is never started so it never completes. If you start it I believe that your program will behave as you would expect, i.e. you will reach the Console.WriteLine statement after about 3 seconds:

    private Task Delay(int milliseconds) {
       return Task.Factory.StartNew(() => Thread.Sleep(milliseconds));
      }
    
    Please remember to mark helpful posts as answer and/or helpful.

    • Proposed as answer by PhGr_ Friday, December 12, 2014 1:34 PM
    • Marked as answer by Kristin Xie Friday, December 19, 2014 9:41 AM
    Tuesday, December 9, 2014 10:38 PM

All replies

  • It seems to be simplified from the standpoint of helping people new to the idea of multithreading at least from what I hear. It's hiding a lot of what was happening so it actually can complicate troubleshooting outside of the built-in methods for debugging async commands in visual studio.

    In this case, you're calling an async method directly from a non-async method which doesn't actually wait for things to finish.  You also need to start the task returned from the Delay function as the await keyword won't start the task for you and will just wait indefinitely. 

    This does what you want:

    class Program {
    		static void Main(string[] args) {
    
    			try {
    				AsyncTester tester = new AsyncTester();
                    tester.Test1WaitableOutsideOfAsyncMethod( ).Wait();
    			}
    			catch (Exception e) {
    				Console.WriteLine("Exception:{0}\n{1}", e.Message, e.StackTrace);
    			}
    			finally {
    				Console.WriteLine("<CR> to exit...");
    				Console.ReadLine();
    			}
    		}
    	}
    
    	public class AsyncTester {
            public async Task Test1WaitableOutsideOfAsyncMethod()
            {
                await Task.Run(async ( ) =>
                {
                    float seconds = await WaitForTimeout(3000);
                    Console.WriteLine("{0} seconds", seconds);
                }
                );
            }
    
    		public async void Test1() {
    			float seconds = await WaitForTimeout(3000);
    			Console.WriteLine("{0} seconds", seconds);
    		}
    
    		public async Task<float> WaitForTimeout(int milliseconds) {
    			Stopwatch timer = new Stopwatch();
    			timer.Restart();
    			Task delay1 = Delay(milliseconds);
    			await delay1;
    			timer.Stop();
    			return (float) timer.Elapsed.TotalSeconds;
    		}
    
    		private Task Delay(int milliseconds) {
    			return Task.Run(() => Thread.Sleep(milliseconds));
    		}
    	}


    WinSDK Support Team Blog: http://blogs.msdn.com/b/winsdk/

    Tuesday, December 9, 2014 7:30 PM
  • Actually this is the expected behaviour by your application. The async and await programming keywords are used to make the calls asynchronous (that is literal and general). What they actually do is, that inside the async block, 

    async Task<float> MyTask () {
      // await keywords here..
    }

    Now as soon as the compiler would hit the await keyword, it will return to the main function from where it was called. Thus releasing the thread for any further processes. At the same time, a new Task would be run, to complete this process that needs to be awaited. Once that method is completed, it will come back to this line and execute this function. 

    Why I would ever use async/await?

    Mostly, when you're going to work with resources that are going to take a few time to work and return some data. For example, while working on the Network, while working with the File System to get some files etc. In these cases you're unware of the actual time that will be consumed while writing the response. Some frameworks and applications, such as WPF might freeze their UI while the user is getting the response from the server (network or file) and thus this can cause a freezing state of ~5seconds. 

    At these stages, it is better to use these async await keywords, to get back to the function from where you're calling the Resource data from. This way, instead of freezing the UI, WPF would return to the UI thread, and you can show any other message to the user, like "Downloading..." and then once the Task has completed you can easily just continue your other processes with the resource. 

    There is no other fancy magic thing that these two keywords would do, but just that they assure that the thread won't wait for the actual process to end and then continue to the later step. But, they let you perform other tasks while you're downloading the data and so on. 


    ~!Firewall!~

    Tuesday, December 9, 2014 7:55 PM
  • I tried your suggestion.  My program hangs forever in Main() at:

    tester.Test1WaitableOutsideOfAsyncMethod().Wait();
    

    I put a few breakpoints in.  In the following code:

    public async Task<float> WaitForTimeout(int milliseconds) {
    	Stopwatch timer = new Stopwatch();
    	timer.Restart();
    	Task delay1 = Delay(milliseconds);
    	await delay1;
    	timer.Stop();
    	return (float) timer.Elapsed.TotalSeconds;
    }
    I hit the timer.Restart() call, but I never get to timer.Stop().  Calling "await WaitForTimeout(3000)" would cause this to run, no?

    Tuesday, December 9, 2014 9:00 PM
  • Did you change the body of the Delay function too? Await won't work inside main because main isn't async.

    WinSDK Support Team Blog: http://blogs.msdn.com/b/winsdk/

    Tuesday, December 9, 2014 9:05 PM
  • Not sure what you mean when you say "At these stages, it is better to use these async await keywords, to get back to the function from where you're calling the Resource data from."

    Does this mean execution resumes at the point after the await, but well after the calling function actually returned to the original caller?  Since you mention WPF, I'm going to make the assumption this is best used in UI code (maybe in a View-Model).

    Thanks for trying, but I'm still confused.  If you have a link other than the one I originally posted, please let me know.

    Again, I have no burning desire to use this as I seem to get by just fine with Tasks.  It's just that when I pickup a copy of MSDN magazine, many of the examples use the async/await keywords and I quickly get lost.

    Tuesday, December 9, 2014 9:05 PM
  • The "What Happens in an Async Method" section on the link explains it pretty well: http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx

    When the program reaches the await expression in your Test1() method, the control will return to the caller (Main) and the remainder of the Test1() method will be executed once the WaitForTimeout method has completed. During the time it takes for the WaitForTimeout to execute, the Test1() method is suspended and control returns to the method's caller.

    There is however a bug in your program. The Task returned from the Delay method is never started so it never completes. If you start it I believe that your program will behave as you would expect, i.e. you will reach the Console.WriteLine statement after about 3 seconds:

    private Task Delay(int milliseconds) {
       return Task.Factory.StartNew(() => Thread.Sleep(milliseconds));
      }
    
    Please remember to mark helpful posts as answer and/or helpful.

    • Proposed as answer by PhGr_ Friday, December 12, 2014 1:34 PM
    • Marked as answer by Kristin Xie Friday, December 19, 2014 9:41 AM
    Tuesday, December 9, 2014 10:38 PM
  • You need to play a few sample. Somehow I found it is acceptable. It is pretty hard to do it in Console. Use WPF or Win Store App. You need a UI to get the response that is what I found.

    Check this.

    https://curah.microsoft.com/260313/async-await

    chanmm


    chanmm

    Wednesday, December 10, 2014 2:11 AM
  • No you don't need to be having just a simple WPF application to test the logic I was trying to provide you with, what I was going to ask was, that most of the time when the line of code takes time to complete as getting resources, saving data on disk etc. In those stages, the code looks like to stop or in as WPF, the UI freezes; you cannot interact with the UI any more.

    In these stages (sorry for saying this again), you're going to use an await keyword, the await keyword will pass the control to the main function (or block) from where the method was called. Another Task is generated for that particular time consuming function and as soon as the task is completed, the rest of the code lines in the block of that await keyword are executed. 

    I would like to recommend that you have a look at the link that Magnus has provided you with, it has an image that you can see the visual representation of the process. 


    ~!Firewall!~

    Wednesday, December 10, 2014 6:21 PM
  • When you hit the await then the stuff in there heads off and does it's stuff and control returns to the calling process.

    The task or whatever finishes and control then returns to the code straight after the await.

    It's a little like the stuff after the await is the callback hooked up to the awaited process completion.

    I have a sample which uses this which might explain things in a simple practical way.

    The idea is to have two events - a single and a double click on one control.

    The user clicks once it does a single click handler.

    The user clicks twice it (only) does a double click handler.

    It does this by an await which essentially introduces a brief pause.

    The user can click again before control returns.

    An attached property complicates things and allows the code to store the number of clicks on the control.

            private async void btn_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
            {
                FrameworkElement ctrl = (FrameworkElement)sender;
                ClickAttach.SetClicks(ctrl, (int)e.ClickCount);
                await Task.Delay(300);
                DoClickStuff(ctrl);
            }
    
            private void DoClickStuff(FrameworkElement ctrl)
            {
                int clicks = ClickAttach.GetClicks(ctrl);
                ClickAttach.SetClicks(ctrl, 0);
                if (clicks > 0)
                {
                    if (clicks == 1)
                    {
                        txtResult.Text = "Single";
                        Debug.WriteLine("Single");
                        // Do your single click stuff 
                    }
                    else
                    {
                        txtResult.Text = "Double";
                        Debug.WriteLine("Double");
                        // Do your double click stuff 
                    }
                }
            }

    That await there introduces a slight delay.

    Through the magic of async this isn't blocking and control returns to call DoSomething a bit later.

    In the meantime the user might have clicked again and the handler will fire again and store the number of clicks as 2.

    Dosomething then checks the click count, stops itself doing anything for a second click and branches.

    Sample:

    https://gallery.technet.microsoft.com/Single-and-Double-Click-d60d8081


    Hope that helps
    Please don't forget to upvote posts which you like and mark those which answer your question.

    Wednesday, December 10, 2014 6:35 PM
  • The link that Magnus provided is where I started.
    Friday, December 12, 2014 1:27 PM
  • I figured that out after a while.  Thanks, it did work after this change.
    Friday, December 12, 2014 1:28 PM
  • Thanks for another example and thanks to all who replied.  I think I know enough to use async and await.  Having said that, I don't foresee a scenario where I will use it.

    I understand Tasks, threads and asynchronous programming very well.  Have been doing it for over 30 years (counting my time doing embedded work with various RTOSes).  If a developer has been doing synchronous, event driven windows programming for years, this may be a viable paradigm in which to work.  For me, I'll stick to Tasks, and ContinueWith(), as that makes things much clearer.  At least for me.


    • Edited by John-CI Friday, December 12, 2014 1:38 PM
    Friday, December 12, 2014 1:37 PM
  • ..and where did you find yourself lost in that link? 

    It would be better, if you provide the details where you got stuck from that page, because MSDN resources are sometimes very complex and are meant for the experts (IMO). 


    ~!Firewall!~

    Friday, December 12, 2014 2:16 PM
  • Reading the section "What happens in an Async method" is where I got lost.  The code example with  complex of arrows (3 different colors) did little to explain what's going on.  Orange means "yielding control to caller at an await".  I still don't understand what happens to the caller.  Execution continues?  The blue lines indicate "resuming a suspended process".  This is shown at the same point of there control was yielded.  How this differs from waiting for a task to complete is beyond me.  The method returns a value, so how can control return without returning the value, which is what that task returns (I think)?

    Maybe a sequence diagram or activity diagram with swim lanes would provide a better illustration.


    • Edited by John-CI Sunday, December 14, 2014 5:39 PM
    Sunday, December 14, 2014 5:38 PM