异步编程模式
.NET有三种异步模式编程能力。
- 基于任务的异步模式(TAP )Task-based Asynchronous Pattern
- 该模式使用单一方法表示异步操作的开始和完成,async 和 await 关键词为TAP添加了支持
- TAP 在
.NET Framework 4中引入 - 在 .NET 中异步编程推荐的方法
- 基于事件的异步模式(EAP)Event-based Asynchronous Pattern
- 该模型是旧模型,异步行为是基于事件的
- 这种模式需要后缀为 Async 的方法、一个或多个事件、事件处理的委托类型、EventArg派生类型。
- EAP 在
.NET Framework 2.0中引入 - 不建议再使用
- 异步编程模型(APM)Asynchronous Programming Model
- 该模型是旧模型,使用IAsyncResult提供异步行为,也称为 IAsyncResult 模式
- 该模式下,同步操作需要 Begin 和 End 方法
- 不建议再使用
- APM 在
.NET Framework 1.0中引入
1 基于任务的异步模式(TAP)
TAP建议用于新开发。命名空间在System.Threading.Tasks中。TAP的异步方法和同步方法具有相同的签名。但是有 out 和 ref 参数除外,并且应该避免它,将其作为Task<T>的一部分返回。
TAP设计中也可以增加取消的支持,如果操作允许取消,需要增加 CancellationToken 类型参数。
TAP设计中也可以增加进度通知的支持,需要 IProgress<T>类型参数。
在TAP中,async 和 await 关键字可以异步调用和阻塞异步方法的调用。
下面用一个示例演示TAP基本用法。例子中有 Person 类,类有同步和异步方法。
-
Listen Music同步方法和PlayGame同步方法。/// <summary> /// 同步听歌方法 /// </summary> public void ListenMusic(string music = "将军令") { for (int i = 0; i < 10; i++) { Console.WriteLine("I'm listening {0} ...", music); Thread.Sleep(500); } Console.WriteLine("{0} has completed.", music); } /// <summary> /// 同步打游戏方法 /// </summary> public void PlayGame() { for (int i = 0; i < 10; i++) { Console.WriteLine("In gamming ..."); Thread.Sleep(500); } } -
演示1:同步调用。
Person p = new Person(); Task<string> result = p.ListenMusic(); p.PlayGame();运行结果:同步调用,在主线程顺序输出。

-
演示2:异步调用。
Person p = new Person(); p.ListenMusicAsync(); p.PlayGameAsync();ListenMusicAsync和PlayGameAsync的方法定义:/// <summary> /// 异步听歌方法 /// </summary> public Task<string> ListenMusicAsync(string music = "将军令") { return Task.Run(() => { for (int i = 0; i < 10; i++) { Console.WriteLine("I'm listening {0} time {1}...", music, i); Thread.Sleep(500); } return string.Format("music {0} finish.", music); }); } /// <summary> /// 异步打游戏 /// </summary> /// <returns></returns> public Task PlayGameAsync() { return Task.Run(() => { for (int i = 0; i < 10; i++) { Console.WriteLine("In gamming ..."); Thread.Sleep(500); } return; }); }运行结果:

就像调用普通方法一样,方法就可以异步执行。有时候异步调用存在先后顺序。此时在调用异步操作的方法声明加上
async关键字,调用异步方法使用await关键字,等待异步的操作返回,然后再继续执行。 -
演示3:阻塞调用。
调用异步的外部方法添加
async关键字,调用异步的时候使用await关键字,同时返回值不是Task<string>而是string。public async static void AsyncRunBlock() { Person p = new Person(); string result = await p.ListenMusicAsync(); Console.WriteLine(result); await p.PlayGameAsync(); }然后调用
AsyncRunBlock方法,运行结果如下:
看起来和同步调用一样,但是还是有区别的。同步方法调用听歌和玩游戏都在主线程中顺序执行。异步方法使用TAP,在执行听歌和玩游戏,其实都开启了另外的线程来执行(演示2)并不在主线程,然后我们在主线程控制了两个异步线程的前后顺序。这种技术在客户端和Web网页开发中极其有用,下载等耗时操作不应卡死界面,应该放在UI线程之外来做。

-
演示4:可取消和进度通知。
Person p = new Person(); CancellationTokenSource cts = new CancellationTokenSource(); var progress = new Progress<int>(); progress.ProgressChanged += Progress_ProgressChanged; p.ListenMusicAsync("一首凉凉送给你", cts, progress); p.PlayGameAsync(); Thread.Sleep(1500);// 1.5s后取消 cts.Cancel();进度通知的事件:
private static void Progress_ProgressChanged(object sender, int e) { Console.WriteLine("Receive Report:{0}%", e); }取消异步程序执行还有其他的用法,比如:
CancellationTokenSource cts = new CancellationTokenSource(2000); // 2S 后取消任务,不用显式调用Cancel方法 // 或者 cts.CancelAfter(3000); // 取消任务,并在3s后执行运行结果:在听歌到30%的时候任务被取消,但是玩游戏的任务没有取消仍继续运行,

2 基于事件的异步模式(EAP)
一般用于执行多个任务,同时仍能响应用户交互的场景。实际上,在 System.Threading 中提供了高性能多线程的所有工具,但是有效使用它需要丰富的经验,而且所需要的工作相对较多。如果是简单的多线程应用程序,BackgroundWorker 比较适合,因为它是一种简单的多线程解决方案。对于复杂的异步应用,可以考虑使用基于事件的异步模式EAP。
EAP的目标在于让开发者像使用事件一样来编写异步的程序,并且可以支持并行执行多个操作。每个操作完成后会收到通知。EAP设计规范上还支持异步取消操作(当取消时,如果正好异步操作执行结束,就会发生“竞争条件”)。
在基于事件的异步模式(EAP)中,可设计为单调用和多调用两种方式。通过重载方法添加一个额外object类型参数来实现。额外参数的核心目的是标识多调用情况下的实例,便于后续的的跟踪。对应的,取消异步的方法,在多调用的情况下,也要有额外的object参数。
在基于事件的异步模式(EAP)中可以增加进度和增量的跟踪事件。多调用情况下需要识别调用的实例。
下面用一个示例演示EAP的基本用法。例子中有有一个 Boy 类,类中一个同步的 ListenMusic 方法和一个异步的 ListenMusicAsync 方法。作为对比,还有一个同步的 PlayGame 方法。
-
ListenMusic和PlayGame同步方法定义/// <summary> /// 同步听歌方法 /// </summary> public void ListenMusic(string music = "将军令") { for (int i = 0; i < 10; i++) { Console.WriteLine("I'm listening {0} ...", music); Thread.Sleep(500); } Console.WriteLine("{0} has completed.", music); } /// <summary> /// 同步打游戏方法 /// </summary> public void PlayGame() { for (int i = 0; i < 10; i++) { Console.WriteLine("In gamming ..."); Thread.Sleep(500); } } -
异步
ListenMusicAsync方法和取消方法,外加一个测试异常的方法。方法中的代码并不十分符合面向对象规范,在此只演示用法。Thread thread = null; bool userCancel = false; public event ProgressChangedEventHandler ProgressChanged; public event ListenMusicCompletedEventHandler ListenMusicCompleted; /// <summary> /// 异步听歌方法 /// </summary> public void ListenMusicAsync(string music = "将军令") { thread = new Thread(() => { int percent = 0; try { for (int i = 0; i < 10 && !userCancel; i++) { Console.WriteLine("I'm listening {0} ...", music); percent += 10; ProgressChangedEventArgs e = new ProgressChangedEventArgs(percent, null); ProgressChanged?.Invoke(e); // 通知进度 Thread.Sleep(500); } ListenMusicCompleted?.Invoke(this, new ListenMusicCompletedEventArgs(music, null, userCancel, null)); // 通知完成 } catch (Exception ex) { ListenMusicCompleted?.Invoke(this, new ListenMusicCompletedEventArgs(music, ex, false, null)); // 通知完成(因取消或异常) } }); thread.Start(); } /// <summary> /// 取消异步方法 /// </summary> public void CancelAsync() { userCancel = true; } /// <summary> /// 测试异常 /// </summary> public void TestAsync() { if (null != thread) thread.Abort(); }以下是完成事件的参数定义:
public class ListenMusicCompletedEventArgs : AsyncCompletedEventArgs { private string music = ""; private bool finish = false; public ListenMusicCompletedEventArgs( string music, Exception e, bool canceled, object state) : base(e, canceled, state) { this.music = music; } public string Music { get { // 异步操作引发异常,推荐使用此方法,事件参数的属性将出现异常 RaiseExceptionIfNecessary(); return music; } } public bool Finish { get { // 异步操作引发异常,推荐使用此方法,事件参数的属性将出现异常 RaiseExceptionIfNecessary(); return finish; } } } -
演示1:同步调用
Boy boy = new Boy(); boy.ListenMusic(); boy.PlayGame();运行结果:顺序在主线程执行。

-
演示2:异步调用
Boy boy = new Boy(); boy.ProgressChanged += Boy_ProgressChanged; boy.ListenMusicCompleted += Boy_ListenMusicCompleted; boy.ListenMusicAsync(); boy.PlayGame();通知回调的代码如下:
private static void Boy_ListenMusicCompleted(object sender, ListenMusicCompletedEventArgs e) { if (e.Cancelled) { Console.WriteLine("Receive Event: music has closed."); //string name = e.Music; // 此处将报错 } else { Console.WriteLine("Receive Event: music {0} is finished. ## User Cancel:{1}", e.Music, e.Cancelled); } } private static void Boy_ProgressChanged(ProgressChangedEventArgs e) { Console.WriteLine("Receive Event: music progress is {0}% ...", e.ProgressPercentage); }运行结果如下:听歌和玩游戏同时进行,定期会收到进度的通知,听歌结束后会收到事件通知。

-
演示3:异步取消
通知回调的代码和上面一样,在开始听歌后取消。
Boy boy = new Boy(); boy.ProgressChanged += Boy_ProgressChanged; boy.ListenMusicCompleted += Boy_ListenMusicCompleted; boy.ListenMusicAsync(); Thread.Sleep(2000); Console.WriteLine("This music is boring.I'll shutdown it."); boy.CancelAsync();运行结果:用户取消听歌后,歌曲播放就结束了。

-
演示4:异常
在异步线程发生异常后,通知事件中的属性不可访问。试图访问会引发异常。
Boy boy = new Boy(); boy.ProgressChanged += Boy_ProgressChanged; boy.ListenMusicCompleted += Boy_ListenMusicCompleted; boy.ListenMusicAsync(); Thread.Sleep(2000); Console.WriteLine("This music is boring.I'll shutdown it."); boy.CancelAsync();
值得注意的是,如果用户取消异步操作,会正常触发
ListenMusicCompleted结束事件,回调参数中Cancelled值是True。此时回调参数中的属性依然不能访问,访问的话会引发上述异常。其实不难理解,用户都取消任务了,再访问属性将变的毫无意义。如果在实现EAP过程中AsyncCompletedEventArgs属性不添加RaiseExceptionIfNecessary方法检验,那么访问属性异常不会发生。这是不建议的。对异步程序来说,有可能会隐藏好多难以发现的问题,建议按照官方推荐方式来实现EAP。
2.1 何时使用EAP
官方描述。
一般原则,尽量使用EAP,如果无法满足一些要求,可能还需要实现 APM (IAsyncResult模式)。
何时实现 EAP 推荐指南:
- 将基于事件的模式用作公开类的异步行为的默认 API。
- 如果类主要用于客户端应用(例如,Windows 窗体),请勿公开IAsyncResult模式。
- 仅在需要满足特定要求时,才公开IAsyncResult 模式。 例如,为了与现有 API 兼容,可能需要公开IAsyncResult 模式。
- 请勿在不公开基于事件的模式的情况下公开 IAsyncResult 模式。
- 如果必须公开IAsyncResult 模式,请以高级选项的形式这样做。 例如,如果生成代理对象,默认生成的是基于事件的模式,并含用于生成IAsyncResult 模式的选项。
- 在IAsyncResult 模式实现的基础之上生成基于事件的模式实现。
- 避免对相同的类公开基于事件的模式和IAsyncResult 模式。 请对“高级”类公开基于事件的模式,并对“低级”类公开IAsyncResult 模式。 例如,比较 WebClient 组件上基于事件的模式与 HttpRequest 类上的IAsyncResult 模式。
- 出于兼容性需要,可以对相同的类公开基于事件的模式和IAsyncResult 模式。 例如,如果已释放使用IAsyncResult 模式的 API,需要保留IAsyncResult 模式,以实现向后兼容性。
- 如果生成的对象模型复杂性远远超过分离实现的好处,请对相同的类公开基于事件的模式和IAsyncResult 模式。 对一个类公开两种模式优于避免公开基于事件的模式。
- 如果必须对一个类公开基于事件的模式和IAsyncResult 模式,请将EditorBrowsableAttribute设置为 Advanced,以将IAsyncResult 模式实现标记为高级功能。 这会指示设计环境(如 Visual Studio IntelliSense)不显示IAsyncResult 属性和方法。 这些属性和方法仍完全可用,这样做只是为了让使用 IntelliSense 的开发人员对 API 更加明确。
何时公开 IAsyncResult 模式的条件:
IAsyncResult 模式比基于事件的模式更适用 的情况有三种:
- 对 IAsyncResult 阻止等待操作
- 对多个 IAsyncResult 对象阻止等待操作
- 对 IAsyncResult 轮询完成状态
虽然可以使用基于事件的模式来处理这些情况,但这样做比使用 IAsyncResult 模式更不方便。
开发人员经常对性能要求通常很高的服务使用 IAsyncResult 模式。 例如,轮询完成状态就是一种高性能服务器技术。
此外,基于事件的模式的效率低于 IAsyncResult 模式,因为前者创建的对象更多(尤其是EventArgs),并且跨线程同步。
下面列出了一些在决定使用 IAsyncResult 模式时要遵循的建议:
- 仅在特别需要对 WaitHandle 或IAsyncResult 对象的支持时,才公开 IAsyncResult 模式。
- 仅在有使用 IAsyncResult 模式的现有 API 时,才公开 IAsyncResult 模式。
- 如果有基于 IAsyncResult 模式的现有 API,还请考虑在下一个版本中公开基于事件的模式。
- 仅在有高性能要求,且已验证无法通过基于事件的模式满足这些要求,但可以通过 IAsyncResult 模式满足时,才公开 IAsyncResult 模式。
3 异步编程模型(APM)
异步编程模型的核心是 IAsyncResult 接口,这个接口只有 IsCompleted 、AsyncWaitHandle、AsyncState、CompletedSynchronously 四个属性。IAsyncResult的对象存储异步操作的信息。
| 属性 | 说明 |
|---|---|
| IsCompleted | 异步操作是否完成 |
| AsyncWaitHandle | 等待异步完成的句柄(信号量) |
| AsyncState | 用户自定义对象,可包含上下文或异步操作信息【可选的】 |
| CompletedSynchronously | 异步操作是否【同步】完成(在调用异步的线程上,而不是单独的线程池) |
异步操作通过 BeginOperationName 和 EndOperationName 两个方法实现,分别开始和结束异步操作。
-
开始异步操作,使用
BeginOperationName方法- Begin方法具有同步版本方法
OperationName的中的所有参数 - Begin方法还有另一个参数
AsyncCallback委托,在异步完成后自动调用,如不希望调用,设置成null - Begin方法还有另一个参数
Object用户定义对象,一般即AsyncState - Begin方法的返回值是
IAsyncResult - Begin方法执行后,无论异步操作是否结束,都立即返回对调用线程的控制
- 如果Begin方法引发异常,则会在异步操作之前引发异常,并且不会调用回调方法
- Begin方法具有同步版本方法
-
结束异步操作,使用
EndOperationName方法- End 方法用于结束异步操作
OperationName,有一个IAsyncResult参数,是Begin 方法的返回值 - End 方法返回值与
OperationName类型相同 - End 方法调用时,如果
IAsyncResult对应的异步操作没有完成,那么 End 方法将阻塞 - 异步操作引发的异常会从 End 方法抛出。重复调用End方法,和End方法使用未返回的
IAsyncResult参数的情况,应考虑引发InvalidOperationException。
- End 方法用于结束异步操作
-
异步操作的阻塞,同步执行
异步编程模型(APM)中使用阻塞实现程序同步执行有三种方式:调用
EndOperationName、使用IAsyncResult中的AsyncWaitHandle、使用时间轮询IsCompleted。 -
使用委托进行异步编程
委托有
Invoke同步执行方法,和BeginInvoke、EndInvoke异步方法,对同步方法使用委托就可以实现异步编程。
举例说明异步编程APM的使用方法。例子中有两个同步方法ReadBook、ListenMusic。同时使用委托对ReadBook同步方法封装两个异步方法BeginReadBook和EndReadBook。同时还包括一个ReadBookFinishCallback回调方法。以此来演示异步编程模型(APM)中的常用的内容。
-
ReadBook同步方法定义
/// <summary> /// 同步读书方法 /// </summary> /// <returns></returns> public int ReadBook(int planPage) { Console.WriteLine("Begin read book..."); Thread.Sleep(5000); Console.WriteLine("End read book.Total {0} pages.", planPage); return planPage; } -
BeginReadBook和EndReadBook异步方法定义(使用委托封装)封装的
BeginReadBook和EndReadBook异步方法,就是常见的APM异步方法。一般使用此种方式实现异步的框架或者库都是以这种形式提供。/// <summary> /// 同步读书方法(用来自己实现一个Begin方法) /// </summary> public delegate int ReadBookDelegate(int page); /// <summary> /// 异步读书开始方法 /// </summary> public IAsyncResult BeginReadBook(int planPage, AsyncCallback callback) { ReadBookDelegate call = ReadBook; return call.BeginInvoke(planPage, callback, call); } /// <summary> /// 异步读书结束方法 /// </summary> public int EndReadBook(IAsyncResult ar) { ar.AsyncWaitHandle.WaitOne(); var call = (ReadBookDelegate)ar.AsyncState; return call.EndInvoke(ar); } -
ListenMusic同步方法定义/// <summary> /// 听歌方法(用作和异步方法做对比) /// </summary> public void ListenMusic() { for (int i = 0; i < 15; i++) { Thread.Sleep(500); Console.WriteLine("Listening music for {0} minutes.", i); } } -
ReadBookFinishCallback回调函数定义public void ReadBookFinishCallback(IAsyncResult result) { // Get the state object associated with this request. ReadBookDelegate call = (ReadBookDelegate)result.AsyncState; Console.WriteLine("ReadBookFinishCallback and then go to park."); } -
演示1:同步调用
依次调用
ReadBook和ListenMusic同步方法。Console.WriteLine("---- 同步调用 ----"); APM amp = new APM(); amp.ReadBook(34); amp.ListenMusic(); Console.ReadKey();运行结果:同步执行,方法依次调用,读书结束后再进行听歌。

-
演示2:异步调用 + 异步回调
依次调用
BeginReadBook异步方法和ListenMusic同步方法,并且使用回调方法。Console.WriteLine("---- 异步调用 ----"); APM amp = new APM(); amp.BeginReadBook(34, new AsyncCallback(amp.ReadBookFinishCallback)); amp.ListenMusic(); Console.ReadKey();运行结果:方法依次调用,异步调用读书,调用结束后返回对调用线程(主线程)的控制。继续调用听歌的方法。读书和听歌同时进行,在听歌没有结束的时候,读书已经完成,触发
ReadBookFinishCallback回调。
-
演示3:
EndReadBook阻塞实现同步执行依次调用
BeginReadBook 、BeginReadBook异步方法和ListenMusic方法 ,BeginReadBook对ListenMusic阻塞。Console.WriteLine("---- 异步调用-阻塞 ----"); APM amp = new APM(); var result = amp.BeginReadBook(34, null); int pages = amp.EndReadBook(result); amp.ListenMusic(); Console.WriteLine("EndReadBook返回值同ReadBook:{0}", pages);运行结果:在调用
BeginReadBook异步调用后,EndReadBook阻塞,ListenMusic在EndReadBook执行结束(异步执行结束)后才执行。
-
演示4:对
EndReadBook重复调用会出现异常Console.WriteLine("---- 异步调用-异常 ----"); APM amp = new APM(); var result = amp.BeginReadBook(34, null); int pages = amp.EndReadBook(result); pages = amp.EndReadBook(result); amp.ListenMusic(); Console.WriteLine("EndReadBook返回值同ReadBook:{0}", pages);运行结果:这些异常需要开发者及时处理

-
演示5:轮询方式阻塞,实现同步执行
Console.WriteLine("---- 异步调用-异常 ----"); APM amp = new APM(); var result = amp.BeginReadBook(34, null); while (result.IsCompleted != true) { Console.Write("."); Thread.Sleep(500); } Console.WriteLine("轮询结束!!!");运行结果:

-
演示6:
WaitOne阻塞,实现同步执行Console.WriteLine("---- 异步调用-WaitOne阻塞 ----"); APM amp = new APM(); var result = amp.BeginReadBook(34, null); result.AsyncWaitHandle.WaitOne(); amp.ListenMusic();运行结果:

4. 不同异步模式之间互操作
基于任务的异步模式(TAP)虽然是新编程所推荐的,但也不是万能的。有些场景使用基于事件的异步模式(EAP)比较合适。异步编程模型(APM)用起来不太友好,但是EAP的性能要比APM差,对于性能要求高的服务,用APM要比EAP合适的太多。
异步编程模式中的三种方法都有其存在的合理性。在白嫖别人的库的时候,经常遇到不同的异步操作方式。所以互操作就显得很重要,我们可以将APM和EAP迁移到TAP,也可以把TAP迁移成APM和EAP来达到兼容性。
4.1 APM -> TAP
以 Read 方法为例,其 ATP 的实现如下:
// 同步方法
public int Read(byte[] buffer, int offset, int count);
// APM 异步开始方法
public IAsyncResult BeginRead(byte[] buffer, int offset, int count, AsyncCallback callback, object state);
// APM 异步结束方法
public int EndRead(IAsyncResult asyncResult);
我们使用 TaskFactory<T>.FromAsync 方法来实现 TAP 包装:
public static Task<int> ReadAsync(this Stream stream, byte[] buffer, int offset, int count)
{
if (stream == null)
throw new ArgumentNullException("stream");
return Task<int>.Factory.FromAsync(stream.BeginRead, stream.EndRead, buffer, offset, count, null);
}
这种实现类似以下内容:
public static Task<int> ReadAsync(this Stream stream, byte [] buffer, int offset, int count)
{
if (stream == null)
throw new ArgumentNullException("stream");
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(buffer, offset, count, iar =>
{
try {
tcs.TrySetResult(stream.EndRead(iar));
}
catch(OperationCanceledException) {
tcs.TrySetCanceled();
}
catch(Exception exc) {
tcs.TrySetException(exc);
}
}, null);
return tcs.Task;
}
4.2 TAP -> APM
如果现有的基础结构需要 APM 模式,则还需要采用 TAP 实现并在需要 APM 实现的地方使用它。 由于任务可以组合,并且 Task类实现 IAsyncResult,您可以使用一个简单的 helper 函数执行此操作。 以下代码使用 Task 类的扩展,但可以对非泛型任务使用几乎相同的函数。
public static IAsyncResult AsApm<T>(this Task<T> task, AsyncCallback callback, object state)
{
if (task == null)
throw new ArgumentNullException("task");
var tcs = new TaskCompletionSource<T>(state);
task.ContinueWith(t =>
{
if (t.IsFaulted)
tcs.TrySetException(t.Exception.InnerExceptions);
else if (t.IsCanceled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(t.Result);
if (callback != null)
callback(tcs.Task);
}, TaskScheduler.Default);
return tcs.Task;
}
现在,请考虑具有以下 TAP 实现的用例:
public static Task<String> DownloadStringAsync(Uri url)
并且想要提供此 APM 实现:
public IAsyncResult BeginDownloadString(Uri url, AsyncCallback callback, object state);
public string EndDownloadString(IAsyncResult asyncResult);
以下示例演示了一种向 APM 迁移的方法:
public IAsyncResult BeginDownloadString(Uri url, AsyncCallback callback, object state)
{
return DownloadStringAsync(url).AsApm(callback, state);
}
public string EndDownloadString(IAsyncResult asyncResult)
{
return ((Task<string>)asyncResult).Result;
}
4.3 EAP -> TAP
包装EAP比包装 APM 模式更为复杂,因为与 APM 模式相比,EAP 模式的变体更多,结构更少。 为了演示,以下代码包装了 DownloadStringAsync 方法。 DownloadStringAsync 接受 URI,在下载时引发 DownloadProgressChanged 事件,以报告进度的多个统计信息,并在完成时引发 DownloadStringCompleted 事件。 最终在指定 URI 中返回一个字符串,其中包含页面内容。
public static Task<string> DownloadStringAsync(Uri url)
{
var tcs = new TaskCompletionSource<string>();
var wc = new WebClient();
wc.DownloadStringCompleted += (s,e) =>
{
if (e.Error != null)
tcs.TrySetException(e.Error);
else if (e.Cancelled)
tcs.TrySetCanceled();
else
tcs.TrySetResult(e.Result);
};
wc.DownloadStringAsync(url);
return tcs.Task;
}
4.4 等待句柄和TAP
4.4.1 等待句柄 -> TAP
虽然等待句柄不能实现异步模式,但高级开发人员可以在设置等待句柄时使用 WaitHandle 类和 ThreadPool.RegisterWaitForSingleObject方法实现异步通知。 可以包装RegisterWaitForSingleObject 方法以在等待句柄中启用针对任何同步等待的基于任务的替代方法:
public static Task WaitOneAsync(this WaitHandle waitHandle)
{
if (waitHandle == null)
throw new ArgumentNullException("waitHandle");
var tcs = new TaskCompletionSource<bool>();
var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle, delegate { tcs.TrySetResult(true); }, null, -1, true);
var t = tcs.Task;
t.ContinueWith( (antecedent) => rwh.Unregister(null));
return t;
}
使用此方法,可以在异步方法中使用现有 WaitHandle 实现。 例如,若要限制在任何特定时间执行的异步操作数,可以利用信号灯(System.Threading.SemaphoreSlim) 对象)。 可以将并发运行的操作数目限制到 N,方法为:初始化到 N 的信号量的数目、在想要执行操作时等待信号量,并在完成操作时释放信号量 :
static int N = 3;
static SemaphoreSlim m_throttle = new SemaphoreSlim(N, N);
static async Task DoOperation()
{
await m_throttle.WaitAsync();
// do work
m_throttle.Release();
}
4.4.2 TAP -> 等待句柄
正如前面所述,Task 类实现IAsyncResult,且该实现公开IAsyncResult.AsyncWaitHandle属性,该属性会返回在Task完成时设置的等待句柄。 可以获得 WaitHandle的Task,如下所示:
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;

浙公网安备 33010602011771号