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();
}
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();
}
}
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();
}
}
Related
I try to stop process by CancellationToken with timer.
But IsCancellationRequested is always false.
I tried to call cancellationToken.ThrowIfCancellationRequested(); it doesn't work.
public async Task<IReadOnlyList<ISearchableDevice>> SearchAsync(CancellationToken cancellationToken)
{
while (!cancellationToken.IsCancellationRequested)
{
await Task.Delay(100, cancellationToken);
cancellationToken.ThrowIfCancellationRequested(); // doesn't work
}
IReadOnlyList<ISearchableDevice> devices = new List<ISearchableDevice>();
return devices;
}
private void OnStartSearchCommandExecuted(object? p)
{
using (var cts = new CancellationTokenSource(TimeSpan.FromSeconds(3)))
{
try
{
InProgress = true;
DeviceSearcher.SearchAsync(cts.Token)
.ContinueWith(task =>
{
InProgress = false;
}, cts.Token);
}
catch (Exception)
{
// TODO: Add exception handling
}
}
}
Where is the mistake?
The problem is that cts is disposed before timeout is reached. More precisely, it is disposed immediately after SearchAsync reaches first await.
You have at least two ways to solve this
Use await DeviceSearcher.SearchAsync instead of DeviceSearcher.SearchAsync().ContinueWith(). This is the best way, but it will force you to make the OnStartSearchCommandExecuted async.
Remove using block and call Dispose manually inside .ContinueWith (and move other post-searh logic inside ContinueWith and also do not forget call Dispose in case of exceptions (whether exception will occur in synchronous part of SearchAsync (before first await) or in asynchronous part. )).
I have requirement to update ui control when status of dependent service will change. I have this sample code, which polling service api to get status and sends result to recalculate and update ui by main thread:
public void StartObserving() {
this.cts = new CancellationTokenSource();
this.cts.Token.ThrowIfCancellationRequested();
this.isRunning = true;
var token = this.cts.Token;
Task.Run(async () =>
{
try
{
while (this.isRunning)
{
var result = this.serviceAPI.GetStatus();
this.OnServiceStatusChanged(result);
await Task.Delay(3000);
}
}
catch (OperationCanceledException)
{
this.isRunning = false;
}
catch (Exception ex)
{
this.isRunning = false;
this.logger.LogError(ex);
}
}, token);
}
And the problem is when I want to cancel above Task. When I call this.cts.Cancel() in another method in this class, I get Exception 'A task was canceled' on dispatcher which was triggered by EventHandler: OnServiceStatusChanged
How I should properly implement this scenario?
I would simply check whether the token in cancelled in the inner loop, and exit the loop if it is. No need to pass the token to the Task.Run() method.
public void StartObserving()
{
this.cts = new CancellationTokenSource();
var token = this.cts.Token;
Task.Run(async () =>
{
try
{
while (!token.IsCancellationRequested)
{
var result = this.serviceAPI.GetStatus();
this.OnServiceStatusChanged(result);
await Task.Delay(3000);
}
}
catch
{
}
});
}
Tried to simulate this behavior in a console app. Task started, but after calling cts.Cancel(), the task continues to execute... Very strange.
However, I could cancel the task by simply setting this.isRunning to false (instead of calling cts.Cancel()). But I am not sure if this is the solution you want.
If serviceAPI.GetStatus() is a blocking call that waits indeffinitly, then you cannot properly cancel this task.
Proper cancellation of async methods involves marking safe cancellation points with CancellationToken.ThrowIfCancellationRequested().
You would have to rewrite serviceAPI.GetStatus() as an async method that you await the result of. It should contain calls to CancellationToken.ThrowIfCancellationRequested() at points where it can be safely cancelled. You would want to pass the cancellation token in to both that method, and the call to Task.Delay() for optimal performance.
I'm trying to write a wrapper for arbitrary code that will cancel (or at least stop waiting for) the code after a given timeout period.
I have the following test and implementation
[Test]
public void Policy_TimeoutExpires_DoStuff_TaskShouldNotContinue()
{
var cts = new CancellationTokenSource();
var fakeService = new Mock<IFakeService>();
IExecutionPolicy policy = new TimeoutPolicy(new ExecutionTimeout(20), new DefaultExecutionPolicy());
Assert.Throws<TimeoutException>(async () => await policy.ExecuteAsync(() => DoStuff(3000, fakeService.Object), cts.Token));
fakeService.Verify(f=>f.DoStuff(),Times.Never);
}
and the "DoStuff" method
private static async Task DoStuff(int sleepTime, IFakeService fakeService)
{
await Task.Delay(sleepTime).ConfigureAwait(false);
var result = await Task.FromResult("bob");
var test = result + "test";
fakeService.DoStuff();
}
And the implementation of IExecutionPolicy.ExecuteAsync
public async Task ExecuteAsync(Action action, CancellationToken token)
{
var cts = new CancellationTokenSource();//TODO: resolve ignoring the token we were given!
var task = _decoratedPolicy.ExecuteAsync(action, cts.Token);
cts.CancelAfter(_timeout);
try
{
await task.ConfigureAwait(false);
}
catch(OperationCanceledException err)
{
throw new TimeoutException("The task did not complete within the TimeoutExecutionPolicy window of" + _timeout + "ms", err);
}
}
What should happen is that that the test method attempts to take >3000ms and the timeout should occur at 20ms, but this is not happening. Why does my code not timeout as expected?
EDIT:
As requested - the decoratedPolicy is as follows
public async Task ExecuteAsync(Action action, CancellationToken token)
{
token.ThrowIfCancellationRequested();
await Task.Factory.StartNew(action.Invoke, token);
}
If I understand correctly, you're trying to support timeout for a method which doesn't supports timeout / cancellation.
Typically this is done with a starting a timer with required timeout value. If the timer fires first, then you can throw exception. With TPL, you can use Task.Delay(_timeout) instead of a timer.
public async Task ExecuteAsync(Action action, CancellationToken token)
{
var task = _decoratedPolicy.ExecuteAsync(action, token);
var completed = await Task.WhenAny(task, Task.Delay(_timeout));
if (completed != task)
{
throw new TimeoutException("The task did not complete within the TimeoutExecutionPolicy window of" + _timeout + "ms");
}
}
Note: This doesn't stops the execution of _decoratedPolicy.ExecuteAsync method rather it ignores it.
If your method do support cancellation(but not in a timely manner) then it is better to cancel the Task after the timeout. You can do it by creating a Linked Token.
public async Task ExecuteAsync(Action action, CancellationToken token)
{
using(var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(token))
{
var task = _decoratedPolicy.ExecuteAsync(action, linkedTokenSource.Token);
var completed = await Task.WhenAny(task, Task.Delay(_timeout));
if (completed != task)
{
linkedTokenSource.Cancel();//Try to cancel the method
throw new TimeoutException("The task did not complete within the TimeoutExecutionPolicy window of" + _timeout + "ms");
}
}
}
Using CancellationToken means that you're doing cooperative cancellation. Setting CancellationTokenSource.CancelAfter will transition the underlying token to a canceled state after the specified amount of time, but if that token isn't monitored by the calling async method, then nothing will happen.
In order for this to actually generate a OperationCanceledException, you need to call cts.Token.ThrowIfCancellationRequested inside _decoratedPolicy.ExecuteAsync.
For example:
// Assuming this is _decoratedPolicy.ExecuteAsync
public async Task ExecuteAsync(Action action, CancellationToken token)
{
// This is what creates and throws the OperationCanceledException
token.ThrowIfCancellationRequested();
// Simulate some work
await Task.Delay(20);
}
Edit:
In order to actually cancel the token, you need to monitor it at all points where work is executed and execution may timeout. If you cannot make that guarantee, then defer to #SriramSakthivel answer where the actual Task is being discarded, rather than being actually canceled.
You are calling Assert.Throws(Action action) and your anonymous async method is casted to async void. The method will be called asynchronously with Fire&Forget semantics without throwing exception.
However the process would likely crash shortly after due to the uncatched exception in an async void method.
You should call ExecuteAsync synchronously:
[Test]
public void Policy_TimeoutExpires_DoStuff_TaskShouldNotContinue()
{
var cts = new CancellationTokenSource();
var fakeService = new Mock<IFakeService>();
IExecutionPolicy policy = new TimeoutPolicy(new ExecutionTimeout(20), new DefaultExecutionPolicy());
Assert.Throws<AggregateException>(() => policy.ExecuteAsync(() => DoStuff(3000, fakeService.Object), cts.Token).Wait());
fakeService.Verify(f=>f.DoStuff(),Times.Never);
}
or use an async test method:
[Test]
public async Task Policy_TimeoutExpires_DoStuff_TaskShouldNotContinue()
{
var cts = new CancellationTokenSource();
var fakeService = new Mock<IFakeService>();
IExecutionPolicy policy = new TimeoutPolicy(new ExecutionTimeout(20), new DefaultExecutionPolicy());
try
{
await policy.ExecuteAsync(() => DoStuff(3000, fakeService.Object), cts.Token);
Assert.Fail("Method did not timeout.");
}
catch (TimeoutException)
{ }
fakeService.Verify(f=>f.DoStuff(),Times.Never);
}
I've decided to answer my own question here, as while each of the listed answers solved something that I needed to do, they did not identify the root cause of this issue. Many, many thanks to: Scott Chamberlain, Yuval Itzchakov,Sriram Sakthivel, Jeff Cyr. All advice gratefully received.
Root cause/Solution:
await Task.Factory.StartNew(action.Invoke, token);
which you see above in my "decorated policy" returns a Task and await waits only for the outer task. Replacing it with
await Task.Run(async () => await action.Invoke());
gets the correct result.
My code was suffering from a combination of Gotcha #4 and Gotcha #5 from an excellent article on C# async gotchas
The entire article (as well as answers posted to this question) has really improved my overall understanding.
This question already has answers here:
How to cancel a Task in await?
(4 answers)
Closed 3 years ago.
OK, my questions is really simple. Why this code does not throw TaskCancelledException?
static void Main()
{
var v = Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}, new CancellationTokenSource(500).Token).Result;
Console.WriteLine(v); // this outputs 10 - instead of throwing error.
Console.Read();
}
But this one works
static void Main()
{
var v = Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}, new CancellationToken(true).Token).Result;
Console.WriteLine(v); // this one throws
Console.Read();
}
Cancellation in Managed Threads:
Cancellation is cooperative and is not forced on the listener. The listener determines how to gracefully terminate in response to a cancellation request.
You didn't write any code inside your Task.Run method to access your CancellationToken and to implement cancellation - so you effectively ignored the request for cancellation and ran to completion.
There is a difference in canceling a running task, and a task scheduled to run.
After the call to the Task.Run method, the task is only scheduled, and probably have not been executed yet.
When you use the Task.Run(..., CancellationToken) family of overloads with cancellation support, the cancellation token is checked when the task is about to run. If the cancellation token has IsCancellationRequested set to true at this time, an exception of the type TaskCanceledException is thrown.
If the task is already running, it is the task's responsibility to call the ThrowIfCancellationRequested method, or just throw the OperationCanceledException.
According to MSDN, it's just a convenience method for the following:
if (token.IsCancellationRequested)
throw new OperationCanceledException(token);
Note the different kind of exception used in this two cases:
catch (TaskCanceledException ex)
{
// Task was canceled before running.
}
catch (OperationCanceledException ex)
{
// Task was canceled while running.
}
Also note that TaskCanceledException derives from OperationCanceledException, so you can just have one catch clause for the OperationCanceledException type:
catch (OperationCanceledException ex)
{
if (ex is TaskCanceledException)
// Task was canceled before running.
// Task was canceled while running.
}
In the first variant of your code, you're not doing anything to manage the cancellation token.
For example, you're not checking whether token.IsCancellationRequested returns true (and then throwing an exception) or calling ThrowIfCancellationRequested() from your CancellationToken object.
Also, the Task.Run overload you used checks whether the token is already canceled or not when the task is about to start and your code states that token will report cancellation after 500 ms.
So, your code is just ignoring the cancellation request and that's why the task ran to completion.
You should do something like this:
void Main()
{
var ct = new CancellationTokenSource(500).Token;
var v =
Task.Run(() =>
{
Thread.Sleep(1000);
ct.ThrowIfCancellationRequested();
return 10;
}, ct).Result;
Console.WriteLine(v); //now a TaskCanceledException is thrown.
Console.Read();
}
or this, without passing the token, as others already noted:
void Main()
{
var ct = new CancellationTokenSource(500).Token;
ct.ThrowIfCancellationRequested();
var v =
Task.Run(() =>
{
Thread.Sleep(1000);
return 10;
}).Result;
Console.WriteLine(v); //now a TaskCanceledException is thrown.
Console.Read();
}
The second variant of your code works, because you're already initializing a token with a Canceled state set to true. Indeed, as stated here:
If canceled is true, both CanBeCanceled and IsCancellationRequested will be true
the cancellation has already been requested and then the exception TaskCanceledException will be immediately thrown, without actually starting the task.
Another implementation using Task.Delay with token instead it Thread.Sleep.
static void Main(string[] args)
{
var task = GetValueWithTimeout(1000);
Console.WriteLine(task.Result);
Console.ReadLine();
}
static async Task<int> GetValueWithTimeout(int milliseconds)
{
CancellationTokenSource cts = new CancellationTokenSource();
CancellationToken token = cts.Token;
cts.CancelAfter(milliseconds);
token.ThrowIfCancellationRequested();
var workerTask = Task.Run(async () =>
{
await Task.Delay(3500, token);
return 10;
}, token);
try
{
return await workerTask;
}
catch (OperationCanceledException )
{
return 0;
}
}
This seems unintuitive to me:
var cts = new CancellationTokenSource();
cts.Cancel();
var tcs = new TaskCompletionSource<int>();
try
{
tcs.Task.Wait(cts.Token);
}
catch (OperationCanceledException)
{
Console.WriteLine(tcs.Task.Status); //TaskStatus.WaitingForActivation
}
I'd expect it to update the status to TaskStatus.Canceled. What is the rationale behind leaving it in TaskStatus.WaitingForActivation?
You're misunderstanding Wait().
Wait(CancellationToken) allows you to cancel the wait operation.
It has no effect on the underlying task.