为什么CancellationTokenRegistration存在,为什么它实现IDisposable

我一直看到在CancellationTokenRegistration结果中使用Cancellation.Registerusing子句的代码:

 using (CancellationTokenRegistration ctr = token.Register(() => wc.CancelAsync())) { await wc.DownloadStringAsync(new Uri("http://www.hamster.com")); } 

我明白你应该确保你Dispose一个IDisposable ,但为什么它甚至实现IDisposable ? 它必须释放什么资源? 它认为平等的唯一方法。

如果您不Dispose它会发生什么? 你泄漏了什么?

此模式是一种方便的方法,可确保自动调用CancellationTokenRegistration.Unregister() 。 它经常被Stephen Toub用于.NET博客文章的并行编程 ,例如这里 。

我明白你应该确保你处理一个IDisposable,但为什么它甚至实现IDisposable? 它必须释放什么资源? 它认为平等的唯一方法。

IMO,对此的最佳答案可以在微软Mike Liddell的.NET 4 Cancellation Frameworkpost中找到:

当回调注册到CancellationToken ,将捕获当前线程的ExecutionContext ,以便使用完全相同的安全上下文运行回调。 如果需要,可以通过ct.Register()的重载来请求捕获当前线程的同步上下文是可选的。 回调通常存储,然后在请求取消时运行,但如果在请求取消后注册了回调,则回调将立即在当前线程上运行,或者通过当前SynchronizationContext上的Send()如果适用)运行。

当回调被注册到CancellationToken ,返回的对象是CancellationTokenRegistration 。 这是一个可以IDiposable的轻型结构类型,并且处理此注册对象会导致取消注册回调。 保证在Dispose()方法返回后,注册的回调既不运行也不随后开始。 这样做的结果是,如果回调当前正在执行,则CancellationTokenRegistration.Dispose()必须阻止。 因此,所有已注册的回调应该是快速的,并且不会阻塞任何显着的持续时间。

Mike Liddell的另一个相关文档是“在.NET Framework 4中使用取消支持”(UsingCancellationinNET4.pdf) 。

更新后 ,这可在参考源中进行validation 。

同样重要的是要注意,取消回调是使用CancellationTokenSource注册的,而不是使用CancellationToken 。 因此,如果CancellationTokenRegistration.Dispose()没有正确确定范围,则注册将在父CancellationTokenSource对象的生命周期内保持活动状态。 当异步操作的范围结束时,这可能会导致意外的回调,例如:

 async Task TestAsync(WebClient wc, CancellationToken token) { token.Register(() => wc.CancelAsync()); await wc.DownloadStringAsync(new Uri("http://www.hamster.com")); } // CancellationTokenSource.Cancel() may still get called later, // in which case wc.CancelAsync() will be invoked too 

因此, using (或使用try/finally显式调用CancellationTokenRegistration.Dispose()来确定一次性CancellationTokenRegistration范围是很重要的。

为什么它甚至实现IDisposable? 它必须释放什么资源?

IDisposable通常用于与释放资源完全无关的事物; 它是一种方便的方法,可确保在using块结束时完成某些操作,即使发生exception也是如此。 有些人(不是我)认为这样做是滥用Dispose模式。

CancellationToken.Register的情况下,“something”是回调的注销。

请注意,在您在问题中发布的代码中,在CancellationTokenRegistrationusing块几乎肯定是一个错误:因为wc.DownloadStringAsync立即返回,所以在操作有可能被取消之前,回调将立即取消注册,因此,即使发出CancellationToken信号,也永远不会调用wc.CancelAsync 如果等待对wc.DownloadStringAsync的调用是wc.DownloadStringAsync ,因为在这种情况下,在完成wc.DownloadStringAsync之前不会到达using块的wc.DownloadStringAsync (编辑:自问题编辑以来不再正确)

如果您不处理它会发生什么? 你泄漏了什么?

在这种情况下,会发生的是回调未注册。 它可能并不重要,因为它仅由取消令牌引用,并且由于CancellationToken是通常仅存储在堆栈上的值类型,因此当引用超出范围时,引用将消失。 因此在大多数情况下它不会泄漏任何东西,但如果你将CancellationToken存储在堆上的某个位置,它可能会泄漏。 编辑:实际上,这是不正确的; 请参阅Noseratio的答案以获得解释