C# 为什么存在CancellationTokenRegistration以及它为什么实现IDisposable
我一直看到使用C# 为什么存在CancellationTokenRegistration以及它为什么实现IDisposable,c#,.net,task-parallel-library,idisposable,cancellation-token,C#,.net,Task Parallel Library,Idisposable,Cancellation Token,我一直看到使用取消的代码。在CancellationTokenRegistration结果上使用using子句注册: using (CancellationTokenRegistration ctr = token.Register(() => wc.CancelAsync())) { await wc.DownloadStringAsync(new Uri("http://www.hamster.com")); } 我明白了,您应该确保Dispose一个IDisposable,
取消的代码。在CancellationTokenRegistration
结果上使用using
子句注册:
using (CancellationTokenRegistration ctr = token.Register(() => wc.CancelAsync()))
{
await wc.DownloadStringAsync(new Uri("http://www.hamster.com"));
}
我明白了,您应该确保Dispose
一个IDisposable
,但为什么它甚至实现了IDisposable
?它必须释放哪些资源?唯一的方法就是平等
如果不处理它会发生什么?您泄漏了什么?此模式是确保自动调用CancellationTokenRegistration.Unregister()
的方便方法。Stephen Toub经常在他的博客文章中使用它,例如
我明白你应该确保你处理了一个IDisposable,但为什么
它甚至实现了IDisposable吗?它需要什么资源
释放?唯一的方法就是平等
在我看来,最好的答案可以在微软的Mike Liddell的帖子中找到:
当回调注册到CancellationToken
时,当前
线程的ExecutionContext
被捕获,以便运行回调
使用完全相同的安全上下文。占领
当前线程的同步上下文是可选的,可以请求
如果需要,通过ct.Register()
的重载。回调通常是
存储,然后在请求取消时运行,但如果
在请求取消后注册,回调将
在当前线程上立即运行,或在当前线程上通过Send()
运行
SynchronizationContext
如果适用
当回调注册到CancellationToken
时,返回
对象是一个CancellationTokenRegistration
。这是一种轻型结构类型
即IDiposable
,处理此注册对象会导致
要取消注册的回调。保证在
Dispose()
方法已返回,注册的回调既不是
运行nor将随后开始。其结果是
CancellationTokenRegistration.Dispose()
必须阻止回调
目前正在执行。因此,所有注册的回调都应该是快速的
并且在任何显著的持续时间内都不会阻塞
Mike Liddell的另一份相关文件是
已更新,这是可验证的
还需要注意的是,取消回调是用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.Dispose()
显式地使用try/finally)将一次性的CancellationTokenRegistration范围限定为)
为什么它甚至实现了IDisposable?它必须释放哪些资源
IDisposable
通常用于与释放资源完全无关的事情;这是一种方便的方法,可以确保在结束时使用
块执行某些操作,即使发生异常也是如此。有些人(不是我)认为这样做是对Dispose
模式的滥用
在CancellationToken.Register
的情况下,“something”是回调的注销
请注意,在您在问题中发布的代码中,在CancellationTokenRegistration
上使用using
块几乎肯定是一个错误:因为wc.DownloadStringAsync
会立即返回,回调会在有机会取消操作之前立即取消注册,因此,wc.CancelAsync
将永远不会被调用,即使发出了CancellationToken
信号。如果等待调用wc.DownloadStringAsync
,这是有意义的,因为在这种情况下,在wc.DownloadStringAsync
完成之前,不会到达using
块的末尾。(编辑:问题编辑后不再为真)
如果你不处理它会怎么样?你漏了什么
在本例中,发生的情况是回调没有被取消注册。这可能并不重要,因为它只被取消令牌引用,而且由于
CancellationToken
是一种通常只存储在堆栈上的值类型,因此引用在超出范围时将消失。因此,在大多数情况下,它不会泄漏任何信息,但如果将CancellationToken
存储在堆的某个位置,它可能会泄漏。编辑:事实上,这是不正确的;请参阅Noseratio的答案以了解解释它是dispose滥用,它应该有一个Unregister()方法。@HansPassant:它是否为“滥用”取决于人们是否将IDisposable.dispose看作是为了清理资源而存在的,或者是为了确保需要完成的事情,完成(资源清理是“必要操作”的主要类型,但不是唯一类型)。@HansPassant-它在asp.net MVC中也大量使用。如果框架作者自己做,我不认为这是滥用。ExecutionContext不也会被捕获到一个“常规”lambda表达式中吗?这不需要处理…@l3arnon,不,不会的。只有当涉及潜在的线程切换时,框架才会捕获ExecutionContext
。如果为任何“常规”lambda回调捕获/恢复它,将是相当大的开销。更多。关于更新,你确定这是真的吗?注册链接到