简体   繁体   English

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

[英]Why CancellationTokenRegistration exists and why does it implement IDisposable

I've been seeing code that uses Cancellation.Register with a using clause on the CancellationTokenRegistration result: 我一直看到在CancellationTokenRegistration结果中使用Cancellation.Registerusing子句的代码:

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

I get that you should make sure you Dispose an IDisposable , but why does it even implements IDisposable ? 我知道你应该确保你Dispose一个IDisposable ,但为什么它甚至实现IDisposable what resources does it have to release? 有什么资源需要发布? The only methods it has regard equality. 它认为平等的唯一方法。

What happens if you don't Dispose of it? 如果您不Dispose它会发生什么? what do you leak? 你泄漏了什么?

This pattern is a convenient way to make sure CancellationTokenRegistration.Unregister() is called automatically. 此模式是一种方便的方法,可确保自动调用CancellationTokenRegistration.Unregister() It's often used by Stephen Toub in his Parallel Programming with .NET blog posts, eg here . 它经常被Stephen Toub用于.NET博客文章的并行编程 ,例如这里

I get that you should make sure you Dispose an IDisposable, but why does it even implements IDisposable? 我知道你应该确保你处理一个IDisposable,但为什么它甚至实现IDisposable? what resources does it have to release? 有什么资源需要发布? The only methods it has regard equality. 它认为平等的唯一方法。

IMO, the best answer to this can be found in the .NET 4 Cancellation Framework post by Microsoft's Mike Liddell: IMO,对此的最佳答案可以在微软Mike Liddell的.NET 4取消框架文章中找到:

When a callback is registered to a CancellationToken , the current thread's ExecutionContext is captured so that the callback will be run with the the exact same security context . 当回调注册到CancellationToken ,捕获当前线程的ExecutionContext ,以便使用完全相同的安全上下文运行回调。 The capturing of the current thread's synchronization context is optional can be requested via an overload of ct.Register() if required. 如果需要,可以通过ct.Register()的重载来请求捕获当前线程的同步上下文是可选的。 Callbacks are normally stored and then run when cancellation is requested, but if a callback is registered after cancellation has been requested, the callback will run immediately on the current thread, or via Send() on the current SynchronizationContext if applicable. 回调通常存储,然后在请求取消时运行,但如果在请求取消后注册了回调,则回调将立即在当前线程上运行,或者通过当前SynchronizationContext上的Send()如果适用)运行。

When a callback is registered to a CancellationToken , the returned object is a CancellationTokenRegistration . 当回调注册到CancellationToken ,返回的对象是CancellationTokenRegistration This is a light struct type that is IDiposable , and disposing this registration object causes the callback to be deregistered. 这是一个可以IDiposable的轻型结构类型,并且处理此注册对象会导致取消注册回调。 A guarantee is made that after the Dispose() method has returned, the registered callback is neither running nor will subsequently commence. 保证在Dispose()方法返回后,注册的回调既不运行也不随后开始。 A consequence of this is that CancellationTokenRegistration.Dispose() must block if the callback is currently executing. 这样做的结果是,如果回调当前正在执行,则CancellationTokenRegistration.Dispose()必须阻止。 Hence, all registered callbacks should be fast and not block for any significant duration. 因此,所有已注册的回调应该是快速的,并且不会阻塞任何显着的持续时间。

Another relevant document by Mike Liddell is "Using Cancellation Support in .NET Framework 4" (UsingCancellationinNET4.pdf) . Mike Liddell的另一个相关文档是“在.NET Framework 4中使用取消支持”(UsingCancellationinNET4.pdf)

Updated , this is verifiable here in the Reference Source . 更新后 ,这可在参考源中进行验证

It's also important to note, the cancellation callback is registered with the CancellationTokenSource , not with CancellationToken . 同样重要的是要注意,取消回调是使用CancellationTokenSource注册的,而不是使用CancellationToken So, if CancellationTokenRegistration.Dispose() is not correctly scoped, the registration will remain active for the lifetime of the parent CancellationTokenSource object. 因此,如果CancellationTokenRegistration.Dispose()没有正确确定范围,则注册将在父CancellationTokenSource对象的生命周期内保持活动状态。 This may lead to an unexpected callback when the scope of the async operation is over, eg: 当异步操作的范围结束时,这可能会导致意外的回调,例如:

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

Thus, is important to scope the disposable CancellationTokenRegistration with using (or call CancellationTokenRegistration.Dispose() explicitly with try/finally ). 因此, using (或使用try/finally显式调用CancellationTokenRegistration.Dispose()来确定一次性CancellationTokenRegistration范围是很重要的。

why does it even implements IDisposable? 为什么它甚至实现IDisposable? what resources does it have to release? 有什么资源需要发布?

IDisposable is often used for things completely unrelated to releasing resources; IDisposable通常用于与释放资源完全无关的事物; it's a convenient way to ensure something will be done at the end of a using block, even if an exception occurs. 它是一种方便的方法,可确保在using块结束时完成某些操作,即使发生异常也是如此。 Some people (not me) argue that doing this is an abuse of the Dispose pattern. 有些人(不是我)认为这样做是滥用Dispose模式。

In the case of CancellationToken.Register , the "something" is the unregistration of the callback. CancellationToken.Register的情况下,“something”是回调的注销。

Note that in the code you posted in your question, the use of a using block on the CancellationTokenRegistration is almost certainly a mistake: since wc.DownloadStringAsync returns immediately, the callback will be unregistered immediately, before the operation has a chance to be cancelled, so wc.CancelAsync will never be called, even if the CancellationToken is signaled. 请注意,在您在问题中发布的代码中,在 CancellationTokenRegistrationusing块几乎肯定是一个错误:因为 wc.DownloadStringAsync立即返回,所以在操作有可能被取消之前,回调将立即取消注册,因此,即使发出 CancellationToken信号,也不会调用 wc.CancelAsync It would make sense if the call to wc.DownloadStringAsync was awaited, because in that case the end of the using block wouldn't be reached before the completion of wc.DownloadStringAsync . 如果等待对 wc.DownloadStringAsync的调用是 wc.DownloadStringAsync ,因为在这种情况下,在完成 wc.DownloadStringAsync之前将不会到达 using块的 wc.DownloadStringAsync (EDIT: not true anymore since the question was edited) (编辑:自问题编辑以来不再正确)

What happens if you don't Dispose of it? 如果您不处理它会发生什么? what do you leak? 你泄漏了什么?

In this case, what happens is that the callback is not unregistered. 在这种情况下,会发生的是回调未注册。 It probably doesn't really matter, because it's only referenced by the cancellation token, and since CancellationToken is a value type that is typically stored only on the stack, the reference will disappear when it goes out of scope. 它可能并不重要,因为它仅由取消令牌引用,并且由于 CancellationToken是通常仅存储在堆栈上的值类型,因此当引用超出范围时,引用将消失。 So it won't leak anything in most cases, but it might if you store the CancellationToken somewhere on the heap. 因此在大多数情况下它不会泄漏任何东西,但如果你将 CancellationToken存储在堆上的某个地方,它可能会泄漏。 EDIT: actually, that's not correct; 编辑:实际上,这是不正确的; see Noseratio's answer for an explanation 请参阅Noseratio的答案以获得解释

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM