去年8月发布的最新版 .NET Framework 4.5 支持 C# 5.0中两个新的关键字:async, await
这两个关键字能极大的简化传统的C# 异步函数代码:
我们先来看看在Silverlight5.0 .NET 4.0 环境下使用 HttpWebRequest 类来发送POST请求的方法:
var req = (HttpWebRequest)WebRequest.Create("http://192.168.100.117:6080/arcgis/admin/generateToken");
req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
req.BeginGetRequestStream(new AsyncCallback(RequestReady), req);
这段代码首先创建了一个HttpWebRequest对象,然后通过异步的 BeginGetRequestStream 方法来获取用来写入POST 数据的流(通过回调函数RequestReady的传参)
然后在回调函数中,我们获取了用来写数据的 Stream对象,往里面写入数据后,又发起一个异步请求:BeginGetResponse 来获取返回结果
private void RequestReady(IAsyncResult ar)
{
HttpWebRequest request = (HttpWebRequest)ar.AsyncState;
System.IO.Stream stream = request.EndGetRequestStream(ar);
StreamWriter writer = new StreamWriter(stream);
writer.WriteLine("username=id");
writer.WriteLine("&client=IP");
writer.WriteLine("&IP=192.168.100.117");
writer.WriteLine("&password=pwd");
writer.Flush();
writer.Close();
request.BeginGetResponse(new AsyncCallback(ResponseReady), request);
}
这样我们最终通过回调函数ResponseReady 获得了最终想要的结果:
private void ResponseReady(IAsyncResult asyncResult)
{
HttpWebRequest request = asyncResult.AsyncState as HttpWebRequest;
try
{
HttpWebResponse response = (HttpWebResponse)request.EndGetResponse(asyncResult);
this.Dispatcher.BeginInvoke(delegate()
{
System.IO.Stream responseStream = response.GetResponseStream();
StreamReader reader = new StreamReader(responseStream);
token = reader.ReadToEnd();
});
}
catch (WebException webException)
{
MessageBox.Show(webException.Response.ToString());
}
}
若有了async 和 await 这两个关键字的支持,我们的异步请求代码就会变的非常简单:
在使用这两个关键字之前,我们需要了解的是 对于异步代码的支持是在编译阶段完成的,这也意味着这我们必须使用最新的编译器,才能实现对C# 5.0 这两个关键字的支持,VS2010是不支持C# 5.0的。对于编译器为了实现async 和 await,在底层做了大量的工作,具体可以参考这里。
值得庆幸的是,在VS 2012 环境下,微软为我们提供了 针对.NET Framework 4.0 ,Silverlight 5.0 平台的常用异步请求扩展包,我们可以使用VS 2012自带的扩展库管理工具Nugget来获得它。
在Search Online搜索框中输入 AsyncTargetingPack, 就可以找到该扩展包了:
在安装了扩展包后,可以注意到在我们的项目中添加了一个 Microsoft.CompilerServices.AsyncTargetingPack.Silverlight5.dll 的引用
在对象查看器中,我们可以看到这个类库对很多常用的IO方法 以及Web方法都做了扩展:
有了这个extension,我们上文中的POST请求代码就可以简化成如下的样子:
var req = (HttpWebRequest)WebRequest.Create(string.Format("http://{0}:{1}/arcgis/admin/generateToken", serverName, portNumber)); req.Method = "POST";
req.ContentType = "application/x-www-form-urlencoded";
Stream x = await req.GetRequestStreamAsync();
StreamWriter writer = new StreamWriter(x);
writer.WriteLine(string.Format("username={0}", username)); //user with administrator priviliage
writer.WriteLine("&client=requestip");
writer.WriteLine(string.Format("&password={0}", password)); //use your own password
writer.Flush();
writer.Close();
WebResponse resp = await req.GetResponseAsync();
Stream x1 = resp.GetResponseStream();
StreamReader reader = new StreamReader(x1);
return reader.ReadToEnd();
添加了AsyncTargetingPack后,HttpWebRequest类增加获取请求流的异步方法:
上图中我们可以注意到这个函数是通过 extension实现的,且awaitable,它返回的是一个Stream对象(异步返回结果都需要用Task封装),这样我们只需要在调用这个函数时在前面加上await就可以让代码执行等待异步的返回结果,即在获取了结果后再执行下一行代码,这样的代码十分符合人类的逻辑思维。同理,获取Web请求返回结果也可以使用await来暂停。
需要注意的是,若要支持await操作符,我们必须将包含await的函数声明为async的方法,即假设上文的代码是运行在一个Button的Click Event Handler中,那么我们必须将这个 Event Handler声明为 async的,即:
private async void Btn_Click(object sender, RoutedEventArgs e)
await async 的意义不仅仅是简化代码逻辑,最重要的是,有了它们的支持,可以实现并发异步请求(Concurrent asynchronous calls)。
例如一个常见的需求,我们使用WebClient通过一个循环来Download 不同Uri的数据,使用以下代码,在Visual Studio中是不支持的(runtime error: NotSupportedException was unhandled by user code. "Request does not support concurrent I/O operations."):
如果有了await的支持,我们就可以轻易实现循环并发异步请求:
foreach (string folderName in ss.folders)
{
string subFolderServiceJson = await wc.DownloadStringTaskAsync(string.Format("http://{0}:{1}/arcgis/admin/services/{2}?f=json&token={3}", server_tbx.Text, port_number_tbx.Text, folderName, s));
// do sth. with subFolderServiceJson
}