Reputation: 1213
Contrived example, but suppose I have the following in an async method:
var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
cts.Token.Register(Callback);
SomethingThatMightThrow();
await Task.Delay(10000, cts.Token);
This works as expected insofar as after a couple of seconds Callback is called. However, I want to dispose the registration after the Task.Delay
, so suppose I make the following modification:
var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
using (cts.Token.Register(Callback))
{
SomethingThatMightThrow();
await Task.Delay(10000, cts.Token);
}
In this case, Callback is not called, presumably because the exception from the await Task.Delay...
causes the registration to be disposed before it gets invoked.
What's the best way of ensuring both that Callback
gets called on cancellation and that the registration always gets disposed?
I did think of the following, but I'm not sure how robust it is:
var cts = new CancellationTokenSource();
cts.CancelAfter(2000);
var ctr = cts.Token.Register(Callback);
try
{
SomethingThatMightThrow();
await Task.Delay(10000, cts.Token);
}
finally
{
if (!cts.Token.IsCancellationRequested)
ctr.Dispose();
}
Upvotes: 3
Views: 1385
Reputation: 149518
it's not so much that the method might not throw the exception, as that it might not throw it as quickly as I'd like. I've noticed some of the Azure SDK async methods, for example, can take quite a while to respond to cancellation being signalled on the token
As per you comment, you can choose to create your own timer to explicitly specify when a method "is taking too long to run" according to your standards. For that you can use Task.WhenAny
:
using (var cts = new CancellationTokenSource())
{
try
{
var cancellationDelayTask = Task.Delay(2000, cts.Token);
var taskThatMightThrow = SomethingThatMightThrowAsync(cts.Token);
if ((await Task.WhenAny(taskThatMightThrow, cancellationDelayTask))
== cancellationDelayTask)
{
// Task.Delay "timeout" finished first.
}
}
catch (OperationCanceledException)
{
Callback();
}
}
Upvotes: 1
Reputation: 456342
CancellationToken.Register
is generally used to interop the new CancellationToken
system with an older system that uses some other kind of notification for cancellation. It is not intended for use as a general-purpose "cancellation callback".
If you want to respond some way when an operation is canceled, then you just catch the appropriate exception:
using (var cts = new CancellationTokenSource())
{
cts.CancelAfter(2000);
SomethingThatMightThrow();
try
{
await Task.Delay(10000, cts.Token);
}
catch (OperationCanceledException)
{
Callback();
}
}
Upvotes: 2