骨架代码:
class Thing
{
public async Task<int> RunAsync(CancellationToken cancellationToken)
{
cancellationToken.Register(() =>
{
//doesn't accept a cancellation, this kills it
this.tcpClient.Dispose();
});
...
}
public void Dispose()
{
tcpClient.Dispose();
tcpClient = null;
}
}
。
var cancellation = new CancellationTokenSource();
var x = new Thing();
await x.RunAsync(cancellation.Token);
x.Dispose();
//yes I know this is odd in this example, it's just to demo
cancellation.Cancel();
我发现,如果RunAsync
已经完成,并且对象已释放,那么取消关联的CancellationTokenSource
仍然会调用注册的 lambda。不出所料,这会导致意外行为 - 我发现了这个问题,因为例如tcpClient
被Dispose()
设置为 null。
既然方法已经退出,为什么cancellationToken
(值类型(仍然存在?这是一个很好的提醒,在 C# 中,对象在您停止使用它们后可能存在,但我认为方法范围的实体会消失。
这里应该有什么不同?Register
是一种危险的使用方法还是我只是滥用它?
CancellationToken.Register
返回一次性CancellationTokenRegistration
。通过处置注册结束注册。
因此,将您的注册包装在 using 块中,一旦块结束,取消将不再调用回调。
using (cancellationToken.Register(() =>
{
//doesn't accept a cancellation, this kills it
this.tcpClient.Dispose();
}))
{
// your code...
}
我仍然建议使你的代码健壮,以防this.tcpClient
为空。我不确定,但是如果我们在注册的回调中放置一个长时间的睡眠并从单独的线程取消,则仍然可以开始执行回调,然后完成RunAsync
,释放Thing
,然后到达回调中的点,访问this.tcpClient
。