Asynchronous programming in C# 5

转载 2012年03月27日 11:12:48

One of the more radical design decisions in the forthcoming Windows Runtime is that no API may, even potentially, take more than 50 milliseconds to complete.  Operations that could take longer than that will instead have a ‘kick off this operation’ API that returns immediately without waiting for the result of the operation.  The reason for this is that Microsoft want Windows 8 Metro applications to be ‘fast and fluid’ — with the immediacy of touch-based UIs, even small hiccups in responsiveness are more obvious and jarring than with a mouse or keyboard.  From a UI point of view, therefore, this is a very helpful design policy.

From a developer point of view, though, it makes life a bit tricky.  When we call read from a file or make a WCF service call, it’s usually because we want to do something with the result.  When the result is guaranteed to be available when the file read or WCF service API returns, we can write our code in a top-to-bottom form which is easy to understand and easy to reason about.

string url = ReadUrlFromFile(filename);
string contentOfUrl = HttpGetFromUrl(url);

APIs like this are called synchronous or blocking.  Synchronous APIs are easy to use and understand, but your entire program (well, the current thread) is unresponsive while you’re inside one.  The API can’t return control to your code to do other tasks, because it can’t deliver the results yet.

The style of having a ‘kick off’ API which returns immediately is called asynchronous or non-blocking.  Programming with asynchronous APIs is more difficult, because you can’t just return the results into a variable and keep going:

string url = BeginReadUrlFromFile(filename);  // Won't work -- file read hasn't completed when BeginRead returns
string contentOfUrl = BeginHttpGetFromUrl(url);  // Ditto

Instead, you have to put the code that uses the results into a callback, which the slow operation will invoke when it’s good and ready:

BeginReadUrlFromFile(filename, url => {
    BeginHttpGetFromUrl(url, contentOfUrl => {

Even a simplified example like this looks pretty ugly.  In real asynchronous code, with more operations being composed, more complex callbacks, conditional logic, early exits and error handling, well, it gets pretty ugly.  And the asynchronous APIs in the real .NET Framework are uglier still, with IAsyncResult objects and paired EndXxx method calls cluttering up the shop.

And yet this is the way our users would like us to work, and the way we will have to work if we want to target the Windows Runtime.

The old solution: use F#

The brainy folk behind F# figured out a way to get the best of both worlds.  F# includes a feature called asynchronous workflows, which are blocks of code introduced by async.  In an asynchronous workflow, you can call asynchronous methods using a syntax that looks just like synchronous code:

async {
  let! url = BeginReadUrlFromFile filename
  let! contentOfUrl = BeginHttpGetFromUrl url

The F# compiler automatically converts this nice readable, understandable code into the ghastly callback-style equivalent, thus giving you the ease of use of top-to-bottom coding with the responsive behaviour of asynchronous calls.

The new solution: use C# 5

Now, the equally brainy folk behind C# have implemented the same feature in C#.  The next version of C#, which is included in Visual Studio 11 beta, introduces two new keywords, async and await.

The async keyword simply indicates that a method makes asynchronous calls.  This is important for callers to know, because it means the method may return before it finishes — the method can yield back to its caller at any asynchronous call.

The await keyword indicates an asynchronous call where we want to keep writing top-to-bottom logic instead of writing out the callbacks by hand.  Here’s how they fit together:

public async void ShowReferencedContent(string filename) {
  string url = await BeginReadFromFile(filename);
  string contentOfUrl = await BeginHttpGetFromUrl(url);

This is much easier to write, read and sanity-check than the callback version, but it’s doing the same thing.  (Actually, it’s quite a bit smarter than the callback version, because compilers don’t get bored and skip over error conditions or screw up early exit logic or ignore threading issues.)

What happens when we call this method?  The first thing that happens is that BeginReadFromFile gets called, with the provided filename and the compiler-generated callback.  BeginReadFromFile returns immediately, but the result isn’t available yet.  So rather than assigning the result to the url variable — which is actually part of the callback — the method then exits and returns to the caller!  The calling method resumes and keeps running its code, even though the called method hasn’t yet finished.

Then, at some later point, the file system completes the read operation.  This means the result is now available, and the runtime schedules the callback.  This doesn’t necessarily happen immediately — the exact timing depends on the synchronisation context.  The callback runs, binds the url variable to the result of the file operation, and calls BeginHttpGetFromUrl.  This also returns immediately, meaning the method exits again.

Finally, the HTTP operation completes and the second callback runs.  This binds the contentOfUrl variable and, as in all bad demos, displays a message box with the result.

What if I want to return a value to the caller?

Async methods can exit before they’ve finished.  So if an async method wants to return a result, it has to recognise that it might return to the caller before that result is available.  For this reason, an async method that returns a value has to have a return type of Task rather than a ‘proper’ value.  A Task represents a chunk of work which will eventually deliver a value, so a caller can examine the returned Task to determine when the result becomes available.  Here’s how an async method looks when returning a value:

public static async Task<string> GetReferencedContent(string filename)
  string url = await BeginReadFromFile(filename);
  string contentOfUrl = await BeginHttpGetFromUrl(url);
  return contentOfUrl;

Notice that the return statement takes a string, even though the return type is Task<string>.  Again, the compiler takes care of transforming the return statement to produce a Task.

Now a caller can call the GetReferencedContent method and either await on it to the string when it becomes available, or wait on it manually, or poll it for completion — whatever suits the way it intends to use the result.

Async-friendly APIs

If you’re familiar with asynchronous programming in .NET 4 and earlier, you’ll be used to paired Begin and End methods, such as WebRequest.BeginGetResponse and WebRequest.EndGetResponse.  These still exist in .NET 4.5, but they don’t work with the await keyword.  (Basically because a BeginXxx method requires an explicit method call inside the callback to get the result, and the compiler couldn’t depend on the EndXxx naming convention.)  Instead, .NET 4.5 provides new methods which return Task objects.  So instead of calling WebRequest.BeginGetResponse, you’ll call WebRequest.GetResponseAsync.  Here’s an example where we finally use some real .NET 4.5 async APIs:

private static async Task<string> GetContent(string url)
  WebRequest wr = WebRequest.Create(url);
  var response = await wr.GetResponseAsync();
  using (var stm = response.GetResponseStream())
    using (var reader = new StreamReader(stm))
      var content = await reader.ReadToEndAsync();
      return content;

Note how similar this looks to the synchronous code using WebRequest.GetResponse() and TextReader.ReadToEnd().  Just stick Async on the end of the API name and stuff an await in front of the call, and you’re good to go!


An introduction to Z-Wave programming in C#

November 30th, 2009 by Henrik Leidecker Jørgensen | Edit this entry Leave a reply » Z-Wave is a...
  • xdruan
  • xdruan
  • 2011年08月25日 21:46
  • 587

微软认证Programming in C# 70-483 MCP 首日封(首日拿下)


MCSD 70-483 Programming in C# 知识梳理(1)

70-483主要考察C#的语言掌握程度。 具体分为4个部分

SQL Programming(5):Presenting Data in a Table View

QSqlQueryModel,QSqlTableModel, 和QSqlRelationalTableMode类可以作为Qt 视图类(例如QListView,QTableView和QTreeView)...

5 Mistakes in programming language design

5 Mistakes in programming language design In the recent years progamming language design experience...

6.087 Practical Programming in C, lec5: Pointers and memory addressing

Pointers and memory addressing. Arrays and pointer arithmetic. Strings.  Searching and sorting algo...
  • jubincn
  • jubincn
  • 2011年10月22日 23:21
  • 550

Using epoll() For Asynchronous Network Programming

General way to implement tcp servers is “one thread/process per connection”. But on high loads this ...

HttpWebRequest - Asynchronous Programming Model/Task.Factory.FromAsyc

潜水三年,今天终于忍不住开始了博客旅程。本人文笔不咋的。所以先从翻译外文开始吧。 声明:本篇文章为我第一次翻译外文,如有言辞不准确的地方,请大家查看原文,欢迎转载,但请注明出处,谢谢。 本篇文章主要讲...

Asynchronous Programming Design Patterns (to be continue)

An Introduction to Asynchronous Programming and Twisted (笔记)

前篇为了更好的理解Twisted研究了异步处理. 我所关心的不是怎么样用Twisted去编写异步处理server或client, 我更关心Twisted是怎么样用python实现这个异步框架, 他de...
您举报文章:Asynchronous programming in C# 5