23

我一直在看到在结果中使用Cancellation.Register带有using子句的代码CancellationTokenRegistration

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

我知道你应该确保你Dispose是一个IDisposable,但为什么它甚至实现IDisposable?它必须释放哪些资源?它唯一的方法是关于平等。

如果你不这样做会发生Dispose什么?你漏什么?

4

2 回答 2

22

此模式是确保CancellationTokenRegistration.Unregister()自动调用的便捷方式。Stephen Toub 在他的Parallel Programming with .NET博客文章中经常使用它,例如这里

我知道你应该确保你 Dispose 一个 IDisposable,但为什么它甚至实现了 IDisposable?它必须释放哪些资源?它唯一的方法是关于平等。

IMO,对此的最佳答案可以在Microsoft 的 Mike Liddell的.NET 4 Cancellation Framework帖子中找到:

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

当回调注册到 aCancellationToken时,返回的对象是 a CancellationTokenRegistration。这是一个轻量级的结构类型,IDiposable处理这个注册对象会导致回调被注销。保证在 Dispose()方法返回后,注册的回调既不会运行也不会随后开始。这样做的结果是, CancellationTokenRegistration.Dispose()如果回调当前正在执行,则必须阻塞。因此,所有注册的回调都应该是快速的,并且不会阻塞很长时间。

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

更新,这在参考源中是可验证的。

同样重要的是要注意,取消回调是用 注册的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

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

于 2014-02-09T00:02:19.570 回答
4

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

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

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

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

如果你不丢弃它会发生什么?你漏什么?

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

于 2014-05-04T01:07:32.017 回答