Skeleton code:
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();
I discovered that if RunAsync has already completed, and the object disposed, then cancelling the associated CancellationTokenSource still calls the registered lambda. Unsurprisingly this causes unexpected behaviour - I found the issue since tcpClient was set to null by Dispose() for example.
Since the method has already exited, why does cancellationToken (a value type) still exist at all? It's a good reminder that in C#, objects may exist after you stop using them but I would have thought method-scoped entities would have disappeared.
What should be different here? Is Register a dangerous method to use or am I just mis-using it?