.NET有三种异步模式编程能力。
基于任务的异步模式(TAP )Task-based Asynchronous Pattern
.NET Framework 4
中引入基于事件的异步模式(EAP)Event-based Asynchronous Pattern
.NET Framework 2.0
中引入?
.NET Framework 1.0
中引入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%的时候任务被取消,但是玩游戏的任务没有取消仍继续运行,
一般用于执行多个任务,同时仍能响应用户交互的场景。实际上,在 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。
官方描述。
一般原则,尽量使用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 模式。
异步编程模型的核心是 IAsyncResult
接口,这个接口只有 IsCompleted
、AsyncWaitHandle
、AsyncState
、CompletedSynchronously
四个属性。IAsyncResult
的对象存储异步操作的信息。
属性 | 说明 |
---|---|
IsCompleted | 异步操作是否完成 |
AsyncWaitHandle | 等待异步完成的句柄(信号量) |
AsyncState | 用户自定义对象,可包含上下文或异步操作信息【可选的】 |
CompletedSynchronously | 异步操作是否【同步】完成(在调用异步的线程上,而不是单独的线程池) |
异步操作通过 BeginOperationName
和 EndOperationName
两个方法实现,分别开始和结束异步操作。
开始异步操作,使用 BeginOperationName
方法
OperationName
的中的所有参数AsyncCallback
委托,在异步完成后自动调用,如不希望调用,设置成null
Object
用户定义对象,一般即 AsyncState
IAsyncResult
结束异步操作,使用 EndOperationName
方法
OperationName
,有一个 IAsyncResult
参数,是Begin 方法的返回值OperationName
类型相同IAsyncResult
对应的异步操作没有完成,那么 End 方法将阻塞IAsyncResult
参数的情况,应考虑引发InvalidOperationException
。异步操作的阻塞,同步执行
异步编程模型(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
阻塞实现同步执行
依次调用 BeginReadBoo
k 、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();
运行结果:
基于任务的异步模式(TAP)虽然是新编程所推荐的,但也不是万能的。有些场景使用基于事件的异步模式(EAP)比较合适。异步编程模型(APM)用起来不太友好,但是EAP的性能要比APM差,对于性能要求高的服务,用APM要比EAP合适的太多。
异步编程模式中的三种方法都有其存在的合理性。在白嫖别人的库的时候,经常遇到不同的异步操作方式。所以互操作就显得很重要,我们可以将APM和EAP迁移到TAP,也可以把TAP迁移成APM和EAP来达到兼容性。
以 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;
}
如果现有的基础结构需要 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;
}
包装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;
}
虽然等待句柄不能实现异步模式,但高级开发人员可以在设置等待句柄时使用 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();
}
正如前面所述,Task
类实现IAsyncResult
,且该实现公开IAsyncResult.AsyncWaitHandle
属性,该属性会返回在Task
完成时设置的等待句柄。 可以获得 WaitHandle
的Task
,如下所示:
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
原文:https://www.cnblogs.com/matthewbin/p/15233108.html