如何使用 CancellationToken 属性的使用无效

2238人阅读
异步编程中的最佳做法
近日来,涌现了许多关于 Microsoft .NET Framework 4.5 中新增了对 async 和 await 支持的信息。&本文旨在作为学习异步编程的“第二步”;我假设您已阅读过有关这一方面的至少一篇介绍性文章。&本文不提供任何新内容,Stack
Overflow、MSDN 论坛和 async/await FAQ 这类在线资源提供了同样的建议。&本文只重点介绍一些淹没在文档海洋中的最佳做法。
本文中的最佳做法更大程度上是“指导原则”,而不是实际规则。&其中每个指导原则都有一些例外情况。&我将解释每个指导原则背后的原因,以便可以清楚地了解何时适用以及何时不适用。&图
1&中总结了这些指导原则;我将在以下各节中逐一讨论。
图 1 异步编程指导原则总结
避免 Async Void
最好使用 async Task 方法而不是 async void 方法
事件处理程序
始终使用 Async
不要混合阻塞式代码和异步代码
控制台 main 方法
配置上下文
尽可能使用 ConfigureAwait(false)
需要上下文的方法
避免 Async Void
Async 方法有三种可能的返回类型: Task、Task&T& 和 void,但是 async 方法的固有返回类型只有 Task 和 Task&T&。&当从同步转换为异步代码时,任何返回类型
T 的方法都会成为返回 Task&T& 的 async 方法,任何返回 void 的方法都会成为返回 Task 的 async 方法。&下面的代码段演示了一个返回 void
的同步方法及其等效的异步方法:
&&&&&&&&&&&&MyMethod(){&&//&Do&synchronous&work.&&&&&&&&&&Thread.Sleep(1000);}async&Task&MyMethodAsync(){&&//&Do&asynchronous&work.&&&&&&&&&&await&Task.Delay(1000);}&&&&&&&&
返回 void 的 async 方法具有特定用途: 用于支持异步事件处理程序。&事件处理程序可以返回某些实际类型,但无法以相关语言正常工作;调用返回类型的事件处理程序非常困难,事件处理程序实际返回某些内容这一概念也没有太大意义。&事件处理程序本质上返回
void,因此 async 方法返回 void,以便可以使用异步事件处理程序。&但是,async void 方法的一些语义与 async Task 或 async Task&T&
方法的语义略有不同。
Async void 方法具有不同的错误处理语义。&当
async Task 或 async Task&T& 方法引发异常时,会捕获该异常并将其置于 Task 对象上。&对于 async void 方法,没有 Task 对象,因此
async void 方法引发的任何异常都会直接在 SynchronizationContext(在 async void 方法启动时处于活动状态)上引发。&图
2&演示本质上无法捕获从 async void 方法引发的异常。
图 2 无法使用 Catch 捕获来自 Async Void 方法的异常
&&&&&&&&&&&&async&&ThrowExceptionAsync(){&&&&InvalidOperationException();}&&AsyncVoidExceptions_CannotBeCaughtByCatch(){&&&&{&&&&ThrowExceptionAsync();&&}&&&(Exception)&&{&&&&//&The&exception&is&never&caught&here!&&&&&&&&&&;&&}}&&&&&&&&
可以通过对 GUI/ASP.NET 应用程序使用 AppDomain.UnhandledException 或类似的全部捕获事件观察到这些异常,但是使用这些事件进行常规异常处理会导致无法维护。
Async void 方法具有不同的组合语义。&返回
Task 或 Task&T& 的 async 方法可以使用 await、Task.WhenAny、Task.WhenAll 等方便地组合而成。&返回 void 的 async
方法未提供一种简单方式,用于向调用代码通知它们已完成。&启动几个 async void 方法不难,但是确定它们何时结束却不易。&Async
void 方法会在启动和结束时通知 SynchronizationContext,但是对于常规应用程序代码而言,自定义 SynchronizationContext 是一种复杂的解决方案。
Async void 方法难以测试。&由于错误处理和组合方面的差异,因此调用
async void 方法的单元测试不易编写。&MSTest 异步测试支持仅适用于返回 Task 或 Task&T& 的 async 方法。&可以安装
SynchronizationContext 来检测所有 async void 方法都已完成的时间并收集所有异常,不过只需使 async void 方法改为返回 Task,这会简单得多。
显然,async void 方法与 async Task 方法相比具有几个缺点,但是这些方法在一种特定情况下十分有用: 异步事件处理程序。&语义方面的差异对于异步事件处理程序十分有意义。&它们会直接在
SynchronizationContext 上引发异常,这类似于同步事件处理程序的行为方式。&同步事件处理程序通常是私有的,因此无法组合或直接测试。&我喜欢采用的一个方法是尽量减少异步事件处理程序中的代码(例如,让它等待包含实际逻辑的
async Task 方法)。&下面的代码演示了这一方法,该方法通过将
async void 方法用于事件处理程序而不牺牲可测试性:
&&&&&&&&&&&&async&&button1_Click(&sender,&EventArgs&e){&&await&Button1ClickAsync();}&async&Task&Button1ClickAsync(){&&//&Do&asynchronous&work.&&&&&&&&&&await&Task.Delay(1000);}&&&&&&&&
如果调用方不希望 async void 方法是异步的,则这些方法可能会造成严重影响。&当返回类型是
Task 时,调用方知道它在处理将来的操作;当返回类型是 void 时,调用方可能假设方法在返回时完成。&此问题可能会以许多意外方式出现。&在接口(或基类)上提供返回
void 的方法的 async 实现(或重写)通常是错误的。某些事件也假设其处理程序在返回时完成。&一个不易察觉的陷阱是将
async lambda 传递到采用 Action 参数的方法;在这种情况下,async lambda 返回 void 并继承 async void 方法的所有问题。&一般而言,仅当
async lambda 转换为返回 Task 的委托类型(例如,Func&Task&)时,才应使用 async lambda。
总结这第一个指导原则便是,应首选 async Task 而不是 async void。&Async
Task 方法更便于实现错误处理、可组合性和可测试性。&此指导原则的例外情况是异步事件处理程序,这类处理程序必须返回 void。&此例外情况包括逻辑上是事件处理程序的方法,即使它们字面上不是事件处理程序(例如
ICommand.Execute implementations)。
始终使用 Async
异步代码让我想起了一个故事,有个人提出世界是悬浮在太空中的,但是一个老妇人立即提出质疑,她声称世界位于一个巨大乌龟的背上。&当这个人问乌龟站在哪里时,老夫人回答:“很聪明,年轻人,下面是一连串的乌龟!”在将同步代码转换为异步代码时,您会发现,如果异步代码调用其他异步代码并且被其他异步代码所调用,则效果最好
— 一路向下(或者也可以说“向上”)。&其他人已注意到异步编程的传播行为,并将其称为“传染”或将其与僵尸病毒进行比较。&无论是乌龟还是僵尸,无可置疑的是,异步代码趋向于推动周围的代码也成为异步代码。&此行为是所有类型的异步编程中所固有的,而不仅仅是新
async/await 关键字。
“始终异步”表示,在未慎重考虑后果的情况下,不应混合使用同步和异步代码。&具体而言,通过调用
Task.Wait 或 Task.Result 在异步代码上进行阻塞通常很糟糕。&对于在异步编程方面“浅尝辄止”的程序员,这是个特别常见的问题,他们仅仅转换一小部分应用程序,并采用同步
API 包装它,以便代码更改与应用程序的其余部分隔离。&不幸的是,他们会遇到与死锁有关的问题。&在
MSDN 论坛、Stack Overflow 和电子邮件中回答了许多与异步相关的问题之后,我可以说,迄今为止,这是异步初学者在了解基础知识之后最常提问的问题: “为何我的部分异步代码死锁?”
3&演示一个简单示例,其中一个方法发生阻塞,等待 async 方法的结果。&此代码仅在控制台应用程序中工作良好,但是在从 GUI 或 ASP.NET 上下文调用时会死锁。&此行为可能会令人困惑,尤其是通过调试程序单步执行时,这意味着没完没了的等待。&在调用
Task.Wait 时,导致死锁的实际原因在调用堆栈中上移。
图 3 在异步代码上阻塞时的常见死锁问题
&&&&&&&&&&&&&&DeadlockDemo{&&&&async&Task&DelayAsync()&&{&&&&await&Task.Delay(1000);&&}&&//&This&method&causes&a&deadlock&when&called&in&a&GUI&or&ASP.NET&context.&&&&&&&&&&&&&Test()&&{&&&&//&Start&the&delay.&&&&&&&&&&var&delayTask&=&DelayAsync();&&&&//&Wait&for&the&delay&to&complete.&&&&&&&&&&delayTask.Wait();&&}}&&&&&&&&
这种死锁的根本原因是 await 处理上下文的方式。&默认情况下,当等待未完成的
Task 时,会捕获当前“上下文”,在 Task 完成时使用该上下文恢复方法的执行。&此“上下文”是当前 SynchronizationContext(除非它是 null,这种情况下则为当前
TaskScheduler)。&GUI 和 ASP.NET 应用程序具有 SynchronizationContext,它每次仅允许一个代码区块运行。&当
await 完成时,它会尝试在捕获的上下文中执行 async 方法的剩余部分。但是该上下文已含有一个线程,该线程在(同步)等待 async 方法完成。&它们相互等待对方,从而导致死锁。
请注意,控制台应用程序不会形成这种死锁。&它们具有线程池
SynchronizationContext 而不是每次执行一个区块的 SynchronizationContext,因此当 await 完成时,它会在线程池线程上安排 async 方法的剩余部分。该方法能够完成,并完成其返回任务,因此不存在死锁。&当程序员编写测试控制台程序,观察到部分异步代码按预期方式工作,然后将相同代码移动到
GUI 或 ASP.NET 应用程序中会发生死锁,此行为差异可能会令人困惑。
此问题的最佳解决方案是允许异步代码通过基本代码自然扩展。&如果采用此解决方案,则会看到异步代码扩展到其入口点(通常是事件处理程序或控制器操作)。&控制台应用程序不能完全采用此解决方案,因为
Main 方法不能是 async。&如果 Main 方法是
async,则可能会在完成之前返回,从而导致程序结束。&图
4演示了指导原则的这一例外情况: 控制台应用程序的 Main 方法是代码可以在异步方法上阻塞为数不多的几种情况之一。
图 4 Main 方法可以调用 Task.Wait 或 Task.Result
&&&&&&&&&&&&Program{&&&&Main()&&{&&&&MainAsync().Wait();&&}&&&async&Task&MainAsync()&&{&&&&&&&&{&&&&&&//&Asynchronous&implementation.&&&&&&&&&&await&Task.Delay(1000);&&&&}&&&&&(Exception&ex)&&&&{&&&&&&//&Handle&exceptions.&&&&&&&&&&}&&}}&&&&&&&&
允许异步代码通过基本代码扩展是最佳解决方案,但是这意味着需进行许多初始工作,该应用程序才能体现出异步代码的实际好处。&可通过几种方法逐渐将大量基本代码转换为异步代码,但是这超出了本文的范围。在某些情况下,使用
Task.Wait 或 Task.Result 可能有助于进行部分转换,但是需要了解死锁问题以及错误处理问题。&我现在说明错误处理问题,并在本文后面演示如何避免死锁问题。
每个 Task 都会存储一个异常列表。&等待
Task 时,会重新引发第一个异常,因此可以捕获特定异常类型(如 InvalidOperationException)。&但是,在 Task 上使用 Task.Wait
或 Task.Result 同步阻塞时,所有异常都会用 AggregateException 包装后引发。&请再次参阅图
4。&MainAsync 中的 try/catch 会捕获特定异常类型,但是如果将 try/catch 置于 Main 中,则它会始终捕获 AggregateException。&当没有
AggregateException 时,错误处理要容易处理得多,因此我将“全局”try/catch 置于 MainAsync 中。
至此,我演示了两个与异步代码上阻塞有关的问题: 可能的死锁和更复杂的错误处理。&对于在
async 方法中使用阻塞代码,也有一个问题。&请考虑此简单示例:
&&&&&&&&&&&&&&NotFullyAsynchronousDemo{&&//&This&method&synchronously&blocks&a&thread.&&&&&&&&&&&&async&Task&TestNotFullyAsync()&&{&&&&await&Task.Yield();&&&&Thread.Sleep(5000);&&}}&&&&&&&&
此方法不是完全异步的。&它会立即放弃,返回未完成的任务,但是当它恢复执行时,会同步阻塞线程正在运行的任何内容。&如果此方法是从
GUI 上下文调用,则它会阻塞 GUI 线程;如果是从 ASP.NET 请求上下文调用,则会阻塞当前 ASP.NET 请求线程。&如果异步代码不同步阻塞,则其工作效果最佳。&图
5&是将同步操作替换为异步替换的速查表。
图 5 执行操作的“异步方式”
执行以下操作…
替换以下方式…
使用以下方式
检索后台任务的结果
Task.Wait 或 Task.Result
等待任何任务完成
Task.WaitAny
await Task.WhenAny
检索多个任务的结果
Task.WaitAll
await Task.WhenAll
等待一段时间
Thread.Sleep
await Task.Delay
总结这第二个指导原则便是,应避免混合使用异步代码和阻塞代码。&混合异步代码和阻塞代码可能会导致死锁、更复杂的错误处理及上下文线程的意外阻塞。&此指导原则的例外情况是控制台应用程序的
Main 方法,或是(如果是高级用户)管理部分异步的基本代码。
配置上下文
在本文前面,我简要说明了当等待未完成 Task 时默认情况下如何捕获“上下文”,以及此捕获的上下文用于恢复 async 方法的执行。&图
3&中的示例演示在上下文上的恢复执行如何与同步阻塞发生冲突从而导致死锁。此上下文行为还可能会导致另一个问题 — 性能问题。&随着异步
GUI 应用程序在不断增长,可能会发现 async 方法的许多小部件都在使用 GUI 线程作为其上下文。&这可能会形成迟滞,因为会由于“成千上万的剪纸”而降低响应性。
若要缓解此问题,请尽可能等待 ConfigureAwait 的结果。&下面的代码段说明了默认上下文行为和
ConfigureAwait 的用法:
&&&&&&&&&&&async&Task&MyMethodAsync(){&&//&Code&here&runs&in&the&original&context.&&&&&&&&&&await&Task.Delay(1000);&&//&Code&here&runs&in&the&original&context.&&&&&&&&&&await&Task.Delay(1000).ConfigureAwait(&&&&continueOnCapturedContext:&);&&//&Code&here&runs&without&the&original&&//&context&(in&this&case,&on&the&thread&pool).&&&&&&&&&&}&&&&&&&&
通过使用 ConfigureAwait,可以实现少量并行性: 某些异步代码可以与 GUI 线程并行运行,而不是不断塞入零碎的工作。
除了性能之外,ConfigureAwait 还具有另一个重要方面: 它可以避免死锁。&再次考虑图
3;如果向 DelayAsync 中的代码行添加“ConfigureAwait(false)”,则可避免死锁。&此时,当等待完成时,它会尝试在线程池上下文中执行
async 方法的剩余部分。&该方法能够完成,并完成其返回任务,因此不存在死锁。&如果需要逐渐将应用程序从同步转换为异步,则此方法会特别有用。
如果可以在方法中的某处使用 ConfigureAwait,则建议对该方法中此后的每个 await 都使用它。&前面曾提到,如果等待未完成的
Task,则会捕获上下文;如果 Task 已完成,则不会捕获上下文。&在不同硬件和网络情况下,某些任务的完成速度可能比预期速度更快,需要谨慎处理在等待之前完成的返回任务。&图
6&显示了一个修改后的示例。
图 6 处理在等待之前完成的返回任务
&&&&&&&&&&&async&Task&MyMethodAsync(){&&//&Code&here&runs&in&the&original&context.&&&&&&&&&&await&Task.FromResult(1);&&//&Code&here&runs&in&the&original&context.&&&&&&&&&&await&Task.FromResult(1).ConfigureAwait(continueOnCapturedContext:&);&&//&Code&here&runs&in&the&original&context.&&&&&&&&&&var&random&=&&Random();&&&delay&=&random.Next(2);&//&Delay&is&either&0&or&1&&await&Task.Delay(delay).ConfigureAwait(continueOnCapturedContext:&);&&//&Code&here&might&or&might&not&run&in&the&original&context.&&&&&&&&&&//&The&same&is&true&when&you&await&any&Task&&//&that&might&complete&very&quickly.&&&&&&&&&&}&&&&&&&&
如果方法中在 await 之后具有需要上下文的代码,则不应使用 ConfigureAwait。&对于
GUI 应用程序,包括任何操作 GUI 元素、编写数据绑定属性或取决于特定于 GUI 的类型(如 Dispatcher/CoreDispatcher)的代码。&对于 ASP.NET
应用程序,这包括任何使用 HttpContext.Current 或构建 ASP.NET 响应的代码(包括控制器操作中的返回语句)。&图
7&演示 GUI 应用程序中的一个常见模式:让 async 事件处理程序在方法开始时禁用其控制,执行某些 await,然后在处理程序结束时重新启用其控制;因为这一点,事件处理程序不能放弃其上下文。
图 7 让 async 事件处理程序禁用并重新启用其控制
&&&&&&&&&&&&async&&button1_Click(&sender,&EventArgs&e){&&button1.Enabled&=&;&&&&{&&&&//&Can't&use&ConfigureAwait&here&...&&&&&&&&&&await&Task.Delay(1000);&&}&&&&{&&&&//&Because&we&need&the&context&here.&&&&&&&&&&button1.Enabled&=&;&&}}&&&&&&&&
每个 async 方法都具有自己的上下文,因此如果一个 async 方法调用另一个 async 方法,则其上下文是独立的。&图
8&演示的代码对图 7&进行了少量改动。
图 8 每个 async 方法都具有自己的上下文
&&&&&&&&&&&&async&Task&HandleClickAsync(){&&//&Can&use&ConfigureAwait&here.&&&&&&&&&&await&Task.Delay(1000).ConfigureAwait(continueOnCapturedContext:&);}&async&&button1_Click(&sender,&EventArgs&e){&&button1.Enabled&=&;&&&&{&&&&//&Can't&use&ConfigureAwait&here.&&&&&&&&&&await&HandleClickAsync();&&}&&&&{&&&&//&We&are&back&on&the&original&context&for&this&method.&&&&&&&&&&button1.Enabled&=&;&&}}&&&&&&&&
无上下文的代码可重用性更高。&尝试在代码中隔离上下文相关代码与无上下文的代码,并尽可能减少上下文相关代码。&在图
8&中,建议将事件处理程序的所有核心逻辑都置于一个可测试且无上下文的 async Task 方法中,仅在上下文相关事件处理程序中保留最少量的代码。&即使是编写
ASP.NET 应用程序,如果存在一个可能与桌面应用程序共享的核心库,请考虑在库代码中使用 ConfigureAwait。
总结这第三个指导原则便是,应尽可能使用 Configure-Await。&无上下文的代码对于
GUI 应用程序具有最佳性能,是一种可在使用部分 async 基本代码时避免死锁的方法。&此指导原则的例外情况是需要上下文的方法。
了解您的工具
关于 async 和 await 有许多需要了解的内容,这自然会有点迷失方向。&图
9&是常见问题的解决方案的快速参考。
图 9 常见异步问题的解决方案
创建任务以执行代码
Task.Run 或 TaskFactory.StartNew(不是&Task
构造函数或 Task.Start)
为操作或事件创建任务包装
TaskFactory.FromAsync 或 TaskCompletionSource&T&
CancellationTokenSource 和 CancellationToken
IProgress&T& 和 Progress&T&
处理数据流
TPL 数据流或被动扩展
同步对共享资源的访问
SemaphoreSlim
异步初始化资源
AsyncLazy&T&
异步就绪生产者/使用者结构
TPL 数据流或 AsyncCollection&T&
第一个问题是任务创建。&显然,async
方法可以创建任务,这是最简单的选项。&如果需要在线程池上运行代码,请使用 Task.Run。&如果要为现有异步操作或事件创建任务包装,请使用
TaskCompletionSource&T&。下一个常见问题是如何处理取消和进度报告。&基类库
(BCL) 包括专门用于解决这些问题的类型: CancellationTokenSource/CancellationToken 和 IProgress&T&/Progress&T&。&异步代码应使用基于任务的异步模式(或称为
TAP,),该模式详细说明了任务创建、取消和进度报告。
出现的另一个问题是如何处理异步数据流。&任务很棒,但是只能返回一个对象并且只能完成一次。&对于异步流,可以使用
TPL 数据流或被动扩展 (Rx)。&TPL 数据流会创建类似于主角的“网格”。&Rx
更加强大和高效,不过也更加难以学习。&TPL 数据流和 Rx 都具有异步就绪方法,十分适用于异步代码。
仅仅因为代码是异步的,并不意味着就安全。&共享资源仍需要受到保护,由于无法在锁中等待,因此这比较复杂。&下面是一个异步代码示例,该代码如果执行两次,则可能会破坏共享状态,即使始终在同一个线程上运行也是如此:
Task&int& GetNextValueAsync(int current);
async Task UpdateValueAsync()
& value = await GetNextValueAsync(value);
问题在于,方法读取值并在等待时挂起自己,当方法恢复执行时,它假设值未更改。&为了解决此问题,使用异步就绪
WaitAsync 重载扩展了 SemaphoreSlim 类。&图
10&演示 SemaphoreSlim.WaitAsync。
图 10 SemaphoreSlim 允许异步同步
SemaphoreSlim mutex = new SemaphoreSlim(1);
Task&int& GetNextValueAsync(int current);
async Task UpdateValueAsync()
& await mutex.WaitAsync().ConfigureAwait(false);
&&& value = await GetNextValueAsync(value);
&&& mutex.Release();
异步代码通常用于初始化随后会缓存并共享的资源。&没有用于此用途的内置类型,但是
Stephen Toub 开发了 AsyncLazy&T&,其行为相当于 Task&T& 和 Lazy&T& 合二为一。&该原始类型在其博客 ()
上进行了介绍,并且在我的 AsyncEx 库 ()
中提供了更新版本。
最后,有时需要某些异步就绪数据结构。&TPL
数据流提供了 BufferBlock&T&,其行为如同异步就绪生产者/使用者队列。&而 AsyncEx 提供了 AsyncCollection&T&,这是异步版本的 BlockingCollection&T&。
我希望本文中的指导原则和指示能有所帮助。&异步真的是非常棒的语言功能,现在正是开始使用它的好时机!
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:58538次
积分:1025
积分:1025
排名:第19982名
原创:40篇
转载:16篇
(1)(2)(2)(1)(1)(5)(4)(1)(1)(1)(2)(1)(2)(5)(1)(1)(1)(1)(1)(1)(1)(2)(2)(2)(1)(1)(2)(3)(2)(3)(3)(1)(1)ManualResetEventSlim 类 (System.Threading)
ManualResetEventSlim 类
.NET Framework 4.5
的简化版本。
System.Threading.ManualResetEventSlim
命名空间:
mscorlib(在 mscorlib.dll 中)
[ComVisibleAttribute(false)]
[HostProtectionAttribute(SecurityAction.LinkDemand, Synchronization = true,
ExternalThreading = true)]
public class ManualResetEventSlim : IDisposable
ManualResetEventSlim 类型公开以下成员。
名称说明使用无信号初始状态初始化 ManualResetEventSlim 类的新实例。使用一个指示是否将初始状态设置为有信号的布尔值初始化 ManualResetEventSlim 类的新实例。使用一个指示是否将初始状态设置为有信号和指定自旋计数的布尔值初始化 ManualResetEventSlim 类的新实例。
名称说明获取是否设置了事件。获取在回退到基于内核的等待操作之前发生的自旋等待数量。 的基础
名称说明释放由 ManualResetEventSlim 类的当前实例占用的所有资源。释放由 ManualResetEventSlim 占用的非托管资源,还可以另外再释放托管资源。确定指定的对象是否等于当前对象。 (继承自 。)允许对象在“垃圾回收”回收之前尝试释放资源并执行其他清理操作。 (继承自 。)作为默认哈希函数。 (继承自 。)。 (继承自 。) 的浅表副本。 (继承自 。)将事件状态设置为非终止状态,从而导致线程受阻。将事件状态设置为有信号,从而允许一个或多个等待该事件的线程继续。返回表示当前对象的字符串。 (继承自 。)阻止当前线程,直到设置了当前 ManualResetEventSlim 为止。 收到信号为止,同时观察 。 为止,同时使用 32 位带符号整数测量时间间隔。 为止,同时使用
测量时间间隔。 为止,并使用 32 位带符号整数测量时间间隔,同时观察 。 为止,并使用
测量时间间隔,同时观察 。You can use this class for better performance than
when wait times are expected to be very short, and when the event does not cross a process boundary.
uses busy spinning for a short time while it waits for the event to become signaled.
When wait times are short, spinning can be much less expensive than waiting by using wait handles.
However, if the event does not become signaled within a certain period of time, ManualResetEventSlim resorts to a regular event handle wait.
说明应用到 此类型或成员的特性具有以下 属性值:Synchronization | ExternalThreading。这 不影响桌面应用程序(通常通过双击图标、键入命令或在浏览器中输入 URL 来启动这些应用程序)。有关更多信息,请参见 类或.The following example shows how to use a ManualResetEventSlim.
For more information about the use of SpinCount and other best practices concerning the use of this type, see .
using System.T
using System.Threading.T
class MRESDemo
static void Main()
MRES_SetWaitReset();
MRES_SpinCountWaitHandle();
// Demonstrates:
ManualResetEventSlim construction
ManualResetEventSlim.Wait()
ManualResetEventSlim.Set()
ManualResetEventSlim.Reset()
ManualResetEventSlim.IsSet
static void MRES_SetWaitReset()
ManualResetEventSlim mres1 = new ManualResetEventSlim(false); // initialize as unsignaled
ManualResetEventSlim mres2 = new ManualResetEventSlim(false); // initialize as unsignaled
ManualResetEventSlim mres3 = new ManualResetEventSlim(true);
// initialize as signaled
// Start an asynchronous Task that manipulates mres3 and mres2
var observer = Task.Factory.StartNew(() =&
mres1.Wait();
Console.WriteLine("observer sees signaled mres1!");
Console.WriteLine("observer resetting mres3...");
mres3.Reset(); // should switch to unsignaled
Console.WriteLine("observer signalling mres2");
mres2.Set();
Console.WriteLine("main thread: mres3.IsSet = {0} (should be true)", mres3.IsSet);
Console.WriteLine("main thread signalling mres1");
mres1.Set(); // This will "kick off" the observer Task
mres2.Wait(); // This won't return until observer Task has finished resetting mres3
Console.WriteLine("main thread sees signaled mres2!");
Console.WriteLine("main thread: mres3.IsSet = {0} (should be false)", mres3.IsSet);
// It's good form to Dispose() a ManualResetEventSlim when you're done with it
observer.Wait(); // make sure that this has fully completed
mres1.Dispose();
mres2.Dispose();
mres3.Dispose();
// Demonstrates:
ManualResetEventSlim construction w/ SpinCount
ManualResetEventSlim.WaitHandle
static void MRES_SpinCountWaitHandle()
// Construct a ManualResetEventSlim with a SpinCount of 1000
// Higher spincount =& longer time the MRES will spin-wait before taking lock
ManualResetEventSlim mres1 = new ManualResetEventSlim(false, 1000);
ManualResetEventSlim mres2 = new ManualResetEventSlim(false, 1000);
Task bgTask = Task.Factory.StartNew(() =&
// Just wait a little
Thread.Sleep(100);
// Now signal both MRESes
Console.WriteLine("Task signalling both MRESes");
mres1.Set();
mres2.Set();
// A common use of MRES.WaitHandle is to use MRES as a participant in
// WaitHandle.WaitAll/WaitAny.
Note that accessing MRES.WaitHandle will
// result in the unconditional inflation of the underlying ManualResetEvent.
WaitHandle.WaitAll(new WaitHandle[] { mres1.WaitHandle, mres2.WaitHandle });
Console.WriteLine("WaitHandle.WaitAll(mres1.WaitHandle, mres2.WaitHandle) completed.");
// Clean up
bgTask.Wait();
mres1.Dispose();
mres2.Dispose();
受以下版本支持:4.5、4受以下版本支持:4受以下版本支持:可移植类库受以下版本支持:Windows 8Windows 8.1, Windows Server 2012 R2, Windows 8, Windows Server 2012, Windows 7, Windows Vista SP2, Windows Server 2008(不支持服务器核心角色), Windows Server 2008 R2(支持带 SP1 或更高版本的服务器核心角色;不支持 Itanium)
并不是.NET Framework 对每个平台的所有版本都提供支持。有关支持的版本的列表,请参见.
All public and protected members of ManualResetEventSlim are thread-safe and may be used concurrently from multiple threads, with the exception of Dispose, which must only be used when all other operations on the ManualResetEventSlim have completed, and Reset, which should only be used when no other threads are accessing the event.
本文是否对您有所帮助?
需要更多代码示例
翻译需要改进
(1500 个剩余字符)
感谢您的反馈

我要回帖

更多关于 属性不同如何婚配 的文章

 

随机推荐