首页 > 代码库 > .Net中的几种异步模式
.Net中的几种异步模式
.Net中的几种异步模式
基于事件的异步模式(EAP)
IAsyncResult接口
简单的异步模式——引入lambda
Task
手动异步编程的问题
在C# 5.0引入async之前,存在几种异步编程模式,比如Event-based Asynchronous Pattern、IAsyncResult接口、Task等等。
基于事件的异步模式(EAP)
private void DumpWebPage(Uri uri)
{
WebClient webClient = new WebClient();
webClient.DownloadStringCompleted += OnDownloadStringCompleted;
webClient.DownloadStringAsync(uri);
}
private void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs eventArgs)
{
m_TextBlock.Text = eventArgs.Result;
}
使用这种模式有点复杂,因为你不得不将一个简单的操作分成两个方法,还需要为完成事件指定一个EventArgs。上例中如果想要为WebClient增加其它请求,但是不想要已经注册的完成事件,那么上述的步骤还得再进行一遍。
IAsyncResult接口
private void LookupHostName()
{
object unrelatedObject = "hello";
Dns.BeginGetHostAddresses("oreilly.com", OnHostNameResolved, unrelatedObject);
}
private void OnHostNameResolved(IAsyncResult ar)
{
object unrelatedObject = ar.AsyncState;
IPAddress[] addresses = Dns.EndGetHostAddresses(ar);
// Do something with addresses
...
}
这种模式解决了残留的事件处理函数,但是,它依然需要将一个简单操作分成两个方法。
基于事件的和基于IAsyncResult接口的异步编程模式都需要将一个操作分割成两个方法。而且都通过Object对象来传递参数,并且强制返回一个Object对象,这样会传递一些并不需要的数据。
简单的异步模式——引入lambda
如果不使用C# 5.0的async的话,将回调做为一个参数传递给方法可能是最简单的异步模式。
void GetHostAddress(string hostName, Action<IPAddress> callback);
如果使用lambda表达式的话,可以直接将回调当做参数传递,而无需另外定义一个方法。
private void LookupHostName()
{
int aUsefulVariable = 3;
GetHostAddress("oreilly.com", address =>
{
// Do something with address and aUsefulVariable
...
});
}
这种模式的缺点是代码的可读性会受到影响。如果使用了多个异步API,可能会出现互相嵌套的lambda表达式。
注意:以上三种模式共同的缺点是任何异常都不会抛出给调用者来处理。而是通过调用异步操作结束方法(EndMethodName)或者获取Result属性来重新抛出异常。
Task
.NET 4.0引入了任务并行库(Task Parallel Library),其中最重要的类是Task和它的泛型类Task
private void LookupHostName()
{
Task<IPAddress[]> ipAddressesPromise = Dns.GetHostAddressesAsync("oreilly.com");
ipAddressesPromise.ContinueWith(_ =>
{
IPAddress[] ipAddresses = ipAddressesPromise.Result;
// Do something with address
...
});
}
使用ContinueWith方法来注册回调。
Task的优势是只需要一个Dns的方法,这使API变得很简洁。所有复杂的逻辑,包括异常处理和同步上下文都封装在Task类中。
手动异步编程的问题
上面的这些异步模式都可以称之为手动异步,它们存在两个共同的问题:
- 分割为两个方法。实际方法和回调方法。使用匿名方法或者lambda表达式缓解了这个问题,使代码看起来更加优雅一些,但同时也需要付出代码犬牙交错、难以跟踪的低价。
- 如果需要不只一个异步操作,甚至是需要循环调用异步操作,那么就不得不使用一个递归方法,和一个普通的循环相比更难以阅读。
private void LookupHostNames(string[] hostNames)
{
LookUpHostNamesHelper(hostNames, 0);
}
private static void LookUpHostNamesHelper(string[] hostNames, int i)
{
Task<IPAddress[]> ipAddressesPromise = Dns.GetHostAddressesAsync(hostNames[i]);
ipAddressesPromise.ContinueWith(() =>
{
IPAddress[] ipAddresses = ipAddressesPromise.Result;
// Do something with address
...
if (i + 1 < hostNames.Length)
{
// 递归调用
LookUpHostNamesHelper(hostNames, i + 1);
}
});
}
.Net中的几种异步模式