---恢复内容开始---
Introduction
There are several ways of doing asynchronous programming in .Net. Visual Studio 2012 introduces a new approach using the ‘await’ and ‘async’ keywords. These tell the compiler to construct task continuations in quite an unusual way.
I found them quite difficult to understand using the Microsoft documentation, which annoyingly keeps saying how easy they are.
This series of articles is intended to give a quick recap of some previous approaches to asynchronous programming to give us some context, and then to give a quick and hopefully easy introduction to the new keywords
Example
By far the easiest way to get to grips with the new keywords is by seeing an example. For this initially I am going to use a very basic example: you click a button on a screen, it runs a long-running method, and displays the results of the method on the screen.
Since this article is about asynchronous programming we will want the long-running method to run asynchronously on a background thread. This means we need to marshal the results back on to the user interface thread to display them.
In the real world the method could be running a report, or calling a web service. Here we will just use the method below, which sleeps to simulate the long-running process:
private string LongRunningMethod(string message) { Thread.Sleep(2000); return "Hello " + message; }
The method will be called asynchronously from a button click method, with the results assigned to the content of a label.
Coding the Example with Previous Asynchronous C# Approaches
There are at least five standard ways of coding the example above in .Net currently. This has got so confusing that Microsoft have started giving the various patterns acronyms, such as the ‘EAP‘ and the ‘APM‘. I’m not going to talk about those as they are effectively deprecated. However it’s worth having a quick look at how to do our example using some of the other approaches.
Coding the Example by Starting our Own Thread
This simple example is fairly easy to code by just explicitly starting a new thread and then using Invoke or BeginInvoke to get the results back onto the UI thread. This should be familiar to you:
private void Button_Click_1(object sender, RoutedEventArgs e) { new Thread(() => { string result = LongRunningMethod("World"); Dispatcher.BeginInvoke((Action)(() => Label1.Content = result)); }).Start(); Label1.Content = "Working..."; }
We start a new thread and hand it the code we want to run. This calls the long-running method and then uses Dispatcher.BeginInvoke to call back onto the user interface thread with the result and update our label.
Note that immediately after we start the new thread we set the content of our label to ‘Working…’. This is to show that the button click method continues immediately on the user interface thread after the new thread is started.
The result is that when we click the button our label says ‘Working…’ almost immediately, and then shows ‘Hello World’ when the long-running method returns. The user interface will remain responsive whilst the long-running thread is running.
Coding the Example Using the Task Parallel Library (TPL)
More instructive is to revisit how we would do this with tasks using the Task Parallel Library. We would typically use a task continuation as below.
private void Button_Click_2(object sender, RoutedEventArgs e) { Task.Run<string>(() => LongRunningMethod("World")) .ContinueWith(ant => Label2.Content = ant.Result, TaskScheduler.FromCurrentSynchronizationContext()); Label2.Content = "Working..."; }
Here we’ve started a task on a background thread using Task.Run. This is a new construct in .Net 4.5. However, it is nothing more complicated than Task.Factory.StartNew with preset parameters. The parameters are the ones you usually want to use. In particular Task.Run uses the default Task Scheduler and so avoids one of the hidden problems with StartNew.
The task calls the long-running method, and does so on a threadpool thread. When it is done a continuation runs using ContinueWith. We want this to run on the user interface thread so it can update our label. So we specify that it should use the task scheduler in the current synchronization context, which is the user interface thread when the task is set up.
Again we update the label after the task call to show that it returns immediately. If we run this we’ll see a ‘Working…’ message and then ‘Hello World’ when the long-running method returns.
Coding the Example Using Async and Await
Code
Below is the full code for the async/await implementation of the example above. We will go through this in detail.
private void Button_Click_3(object sender, RoutedEventArgs e) { CallLongRunningMethod(); Label3.Content = "Working..."; } private async void CallLongRunningMethod() { string result = await LongRunningMethodAsync("World"); Label3.Content = result; } private Task<string> LongRunningMethodAsync(string message) { return Task.Run<string>(() => LongRunningMethod(message)); } private string LongRunningMethod(string message) { Thread.Sleep(2000); return "Hello " + message; }
Asynchronous Methods
The first thing to realize about the async and await keywords is that by themselves they never start a thread. They are a way of controlling continuations, not a way of starting asynchronous code.
As a result the usual pattern is to create an asynchronous method that can be used with async/await, or to use an asynchronous method that is already in the framework. For these purposes a number of new asynchronous methods have been added to the framework.
To be useful to async/await the asynchronous method has to return a task. The asynchronous method has to start the task it returns as well, something that maybe isn’t so obvious.
So in our example we need to make our synchronous long-running method into an asynchronous method. The method will start a task to run the long-running method and return it. The usual approach is to wrap the method in a new method. It is usual to give the method the same name but append ‘Async’. Below is the code to do this for the method in our example:
private Task<string> LongRunningMethodAsync(string message) { return Task.Run<string>(() => LongRunningMethod(message)); }
Note that we could use this method directly in our example without async/await. We could call it and use ‘ContinueWith’ on the return value to effect our continuation in exactly the same way as in the Task Parallel Library code above. This is true of the new async methods in the framework as well.
Async/Await and Method Scope
Async and await are a smart way of controlling continuations through method scope. They are used as a pair in a method as shown below:
private async void CallLongRunningMethod() { string result = await LongRunningMethodAsync("World"); Label3.Content = result; }
Here async is simply used to tell the compiler that this is an asynchronous method that will have an await in it. It’s the await itself that’s interesting.
The first line in the method calls LongRunningMethodAsync, clearly. Remember that LongRunningMethodAsync is returning a long-running task that is running on another thread. LongRunningMethodAsync starts the task and then returns reasonably quickly.
The await keyword ensures that the remainder of the method does not execute until the long-running task is complete. It sets up a continuation for the remainder of the method. Once the long-running method is complete the label content will update: note that this happens on the same thread that CallLongRunningMethod is already running on, in this case the user interface thread.
However, the await keyword does not block the thread completely. Instead control is returned to the calling method on the same thread. That is, the method that called CallLongRunningMethod will execute at the point after the call was made.
The code that calls LongRunningMethod is below:
private void Button_Click_3(object sender, RoutedEventArgs e) { CallLongRunningMethod(); Label3.Content = "Working..."; }
So the end result of this is exactly the same as before. When the button is clicked the label has content ‘Working…’ almost immediately, and then shows ‘Hello World’ when the long-running task completes.
Return Type
One other thing to note is that LongRunningMethodAsync returns a Task<string>, that is, a Task that returns a string. However the line below assigns the result of the task to the string variable called ‘result’, not the task itself.
string result = await LongRunningMethodAsync("World");
The await keyword ‘unwraps’ the task. We could have attempted to access the Result property of the task (string result = LongRunningMethodAsync(“World”).Result. This would have worked but would have simply blocked the user interface thread until the method completed, which is not what we’re trying to do.
I’ll discuss this further below.
Recap
To recap, the button click calls CallLongRunningMethod, which in turn calls LongRunningMethodAsync, which sets up and runs our long-running task. When the task is set up (not when it’s completed) control returns to CallLongRunningMethod, where the await keyword passes control back to the button click method.
So almost immediately the label content will be set to “Working…”, and the button click method will exit, leaving the user interface responsive.
When the task is complete the remainder of CallLongRunningMethod executes as a continuation on the user interface thread, and sets the label to “Hello World”.
Async and Await are a Pair
Async and await are always a pair: you can’t use await in a method unless the method is marked async, and if you mark a method async without await in it then you get a compiler warning. You can of course have multiple awaits in one method as long as it is marked async.
Aside: Using Anonymous Methods with Async/Await
If you compare the code for the Task Parallel Library (TPL) example with the async/await example you’ll see that we’ve had to introduce two new methods for async/await: for this simple example the TPL code is shorter and arguably easier to understand. However, it is possible to shorten the async/await code using anonymous methods, as below. This shows how we can use anonymous method syntax with async/await, although I think this code is borderline incomprehensible:
private void Button_Click_4(object sender, RoutedEventArgs e) { new Action(async () => { string result = await Task.Run<string>(() => LongRunningMethod("World")); Label4.Content = result; }).Invoke(); Label4.Content = "Working..."; }
Using the Call Stack to Control Continuations
Overview of Return Values from Methods Marked as Async
There’s one other fundamental aspect of async/await that we have not yet looked at. In the example above our method marked with the async keyword did not return anything. However, we can make all our async methods return values wrapped in a task, which means they in turn can be awaited on further up the call stack. In general this is considered good practice: it means we can control the flow of our continuations more easily.
The compiler makes it easy for us to return a value wrapped in a task from an async method. In a method marked async the ‘return’ statement works differently from usual. The compiler doesn’t simply return the value passed with the statement, but instead wraps it in a task and returns that instead.
Example of Return Values from Methods Marked as Async
Again this is easiest to see with our example. Our method marked as async was CallLongRunningMethod, and this can be altered to return the string result to the calling method as below:
private async Task<string> CallLongRunningMethodReturn() { string result = await LongRunningMethodAsync("World"); return result; }
We are returning a string (‘return result’), but the method signature shows the return type as Task<string>. Personally I think this is a little confusing, but as discussed it means the calling method can await on this method. Now we can change the calling method as below:
private async void Button_Click_5(object sender, RoutedEventArgs e) { Label5.Content = "Working..."; string result = await CallLongRunningMethodReturn(); Label5.Content = result; }
We can await the method lower down the call stack because it now returns a task we can await on. What this means in practice is that the code sets up the task and sets it running and then we can await the results from the task when it is complete anywhere in the call stack. This gives us a lot of flexibility as methods at various points in the stack can carry on executing until they need the results of the call.
As discussed above when we await on a method returning type Task<string> we can just assign the result to a string as shown. This is clearly related to the ability to just return a string from the method: these are syntactic conveniences to avoid the programmer having to deal directly with the tasks in async/await.
Note that we have to mark our method as ‘async’ in the method signature (‘private async void Button_Click_5′) because it now has an await in it, and they always go together.
What the Code Does
The code above has exactly the same result as the other examples: the label shows ‘Working…’ until the long-running method returns when it shows ‘Hello World’. When the button is clicked it sets up the task to run the long-running method and then awaits its completion both in CallLongRunningMethodReturn and Button_Click_5. There is one slight difference in that the click event is awaiting: previously it exited. However, if you run the examples you’ll see that the user interface remains responsive whilst the task is running.
What’s The Point?
If you’ve followed all the examples so far you may be wondering what the point is of the new keywords. For this simple example the Task Parallel Library syntax is shorter, cleaner and probably easier to understand than the async/await syntax. At first sight async/await are a little confusing.
The answer is that for basic examples async/await don’t seem to me to be adding a lot of value, but as soon as you try to do more complex continuations they come into their own. For example it’s possible to set up multiple tasks in a loop and write very simple code to deal with what happens when they complete, something that is tricky with tasks. I suggest you look at the examples in the Microsoft documentation which do show the power of the new keywords.
Code
The full code for these examples is available to download.
Conclusion
This article has only covered the basics of the async/await keywords, although I think it’s addressed all the things that were confusing me when trying to learn about them from the Microsoft documentation. There are some obvious things it hasn’t covered such as cancelling tasks, exception handling, unwrapping tasks (and why you might need to do that) and how to deal with the reentrancy problems that arise. All of these are covered reasonably well in the documentation.
Personally I think async and await are far from intuitive: the compiler is performing some magic of a kind we don’t usually see in C#. The result is that we are yielding control in the middle of a method to the calling method until some other task is complete. Of course we can do similar things with regular task continuations, but the syntax makes regular continuations look slightly less magical.
However, async/await are a powerful way of controlling multithreaded code once you understand what they are doing. They can make fairly complex threading look simple.
November 21, 2012
Why Starting a New Task in the Task Parallel Library (TPL) Doesn’t Always Start a New Thread
The Task Parallel Library in .Net is a wonderful resource. However, one thing that is a little confusing is that it is actually possible to start a task that runs on the same thread as the current code. That is, we can do Task.StartNew and it still runs on the same thread. This isn’t the behaviour we expect.
This can happen particularly when we are writing .Net user interface applications where we marshal data on to the UI thread.
Consider the sample code below, which runs in the button click event of a simple WPF application. This is a not unusual pattern for a user interface application: we start a Task on a new thread and then perform a continuation on the user interface thread using the current synchronization context, which is the user interface thread synchronization context (TaskScheduler.FromCurrentSynchronizationContext()). We would typically do this so that we could write the results of the Task into the user interface. There are several articles on the internet that describe this.
private void button1_Click(object sender, RoutedEventArgs e) { Debug.WriteLine("UI thread: " + Thread.CurrentThread.ManagedThreadId); Task.Factory.StartNew(() => Debug.WriteLine("Task 1 thread: " + Thread.CurrentThread.ManagedThreadId)) .ContinueWith(ant => { Debug.WriteLine("Continuation on UI thread: " + Thread.CurrentThread.ManagedThreadId); Task.Factory.StartNew(() => Debug.WriteLine("Task 2 thread is UI thread: " + Thread.CurrentThread.ManagedThreadId)); }, TaskScheduler.FromCurrentSynchronizationContext()); }
However, in the code above we then start a new Task from the continuation (task 2). This runs on the user interface thread by default. This is almost certainly not what we are trying to do. We probably want to run something on a background thread, which is what usually happens when we start a Task. As a result it’s slightly hazardous behaviour; we can easily end up blocking the user interface thread because we think the code is running on a background thread.
The output from a couple of button clicks using this code is as below:
UI thread: 8 Task 1 thread: 9 Continuation on UI thread: 8 Task 2 thread is UI thread: 8 UI thread: 8 Task 1 thread: 10 Continuation on UI thread: 8 Task 2 thread is UI thread: 8
The reason for this behaviour is that the user interface thread synchronization context is still in force when we start our second task.
Fortunately, in this case at least there is a simple solution, even if it isn’t so obvious. We can tell the new Task to run explicitly using the default synchronization context instead of the user interface synchronization context when we start it. The simplest syntax for this changes the code as below. Notice it passes TaskScheduler.Default as an argument to Task.Start. The terminology here for a user interface application is a little confusing. The ‘current synchronization context’ is accessed using TaskScheduler.FromCurrentSynchronizationContext, which we typically do from the user interface thread. Then the ’current’ context that results in delegates being queued back to the user interface thread. The ‘default synchronization context’ is the context that results in delegates being queued to the thread pool.
private void button1_Click(object sender, RoutedEventArgs e) { Debug.WriteLine("UI thread: " + Thread.CurrentThread.ManagedThreadId); Task.Factory.StartNew(() => Debug.WriteLine("Task 1 thread: " + Thread.CurrentThread.ManagedThreadId)) .ContinueWith(ant => { Debug.WriteLine("Continuation on UI thread: " + Thread.CurrentThread.ManagedThreadId); Task task2 = new Task(() => Debug.WriteLine("Task 2 thread: " + Thread.CurrentThread.ManagedThreadId)); task2.Start(TaskScheduler.Default); }, TaskScheduler.FromCurrentSynchronizationContext()); }
The output from this is now as we would expect:
UI thread: 9 Task 1 thread: 10 Continuation on UI thread: 9 Task 2 thread: 11 UI thread: 9 Task 1 thread: 11 Continuation on UI thread: 9 Task 2 thread: 10
For more detailed discussion on this see:
http://stackoverflow.com/questions/6800705/why-is-taskscheduler-current-the-default-taskscheduler
---恢复内容结束---