为什么CancellationTokenRegistration存在,为什么它实现IDisposable
我一直看到在CancellationTokenRegistration
结果中使用Cancellation.Register
和using
子句的代码:
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”是回调的注销。
请注意,在您在问题中发布的代码中,在 CancellationTokenRegistration
上using
块几乎肯定是一个错误:因为wc.DownloadStringAsync
立即返回,所以在操作有可能被取消之前,回调将立即取消注册,因此,即使发出CancellationToken
信号,也永远不会调用wc.CancelAsync
。如果等待对 (编辑:自问题编辑以来不再正确) wc.DownloadStringAsync
的调用是wc.DownloadStringAsync
,因为在这种情况下,在完成wc.DownloadStringAsync
之前不会到达using
块的wc.DownloadStringAsync
。
如果您不处理它会发生什么? 你泄漏了什么?
在这种情况下,会发生的是回调未注册。 它可能并不重要,因为它仅由取消令牌引用,并且由于 CancellationToken
是通常仅存储在堆栈上的值类型,因此当引用超出范围时,引用将消失。因此在大多数情况下它不会泄漏任何东西,但如果你将 编辑:实际上,这是不正确的; 请参阅Noseratio的答案以获得解释 CancellationToken
存储在堆上的某个位置,它可能会泄漏。