关闭

Asynchronous programming in C# 5

标签: asynchronousc#callbackstringurlcompiler
415人阅读 评论(0) 收藏 举报
分类:

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);
MessageBox.Show(contentOfUrl);

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
MessageBox.Show(contentOfUrl);

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 => {
      MessageBox.Show(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
  MessageBox.Show(contentOfUrl)
}

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);
  MessageBox.Show(contentOfUrl);
}

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!

0
0

猜你在找
【直播】机器学习&数据挖掘7周实训--韦玮
【套餐】系统集成项目管理工程师顺利通关--徐朋
【直播】3小时掌握Docker最佳实战-徐西宁
【套餐】机器学习系列套餐(算法+实战)--唐宇迪
【直播】计算机视觉原理及实战--屈教授
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之矩阵--黄博士
【套餐】微信订阅号+服务号Java版 v2.0--翟东平
【直播】机器学习之凸优化--马博士
【套餐】Javascript 设计模式实战--曾亮
查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:449154次
    • 积分:7834
    • 等级:
    • 排名:第2637名
    • 原创:314篇
    • 转载:225篇
    • 译文:12篇
    • 评论:28条