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

时间:2014-01-26 18:49:55

标签: c# .net task-parallel-library idisposable cancellation-token

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

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

我知道你应该确保Dispose IDisposable,但为什么它会实现IDisposable?有什么资源需要发布?它只考虑平等的唯一方法。

如果你没有Dispose会怎么样?你泄漏了什么?

2 个答案:

答案 0 :(得分:16)

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

  

我知道你应该确保你处理一个IDisposable,但为什么   它甚至实现了IDisposable?它有什么资源   发布?它只考虑平等的唯一方法。

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

  

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

     

当回调注册到CancellationToken时,返回   对象是CancellationTokenRegistration。这是一种轻型结构类型   即IDiposable,并且处理此注册对象会导致   回调要注销。保证在之后   Dispose()方法已返回,注册的回调都不是   跑步也不会随后开始。这样做的结果是   如果回调是CancellationTokenRegistration.Dispose()必须阻止   目前正在执行因此,所有注册的回调都应该很快   并且不会阻止任何重要的持续时间。

Mike Liddell的另一份相关文件是"Using Cancellation Support in .NET Framework 4" (UsingCancellationinNET4.pdf)

已更新,这是可验证的here in the Reference Source

同样重要的是要注意,取消回调是在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的范围非常重要。

答案 1 :(得分:3)

  

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

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

CancellationToken.Register的情况下,“某事”是取消注册回调。

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

  

如果您不处理它会怎样?你泄漏了什么?

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