TaskCompletionSource cancellation with CancellationTokenSource does not update Task.Status? - c#

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.

Related

How to chain async tasks with a CancellationToken?

I'd like to chain some tasks but conditionally continue with the execution if the CancellationToken I have wasn't fired.
What I'm aiming to achieve is something equivalent to
var cts = new CancellationTokenSource();
var cancellationToken = cts.Token;
var t = Task.Run(async () => {
if (cancellationToken.IsCancellationRequested) return;
await t1();
if (cancellationToken.IsCancellationRequested) return;
await t2();
if (cancellationToken.IsCancellationRequested) return;
await t3();
if (cancellationToken.IsCancellationRequested) return;
await t4();
});
var timeout = Task.Delay(TimeSpan.FromSeconds(4));
var completedTask = await Task.WhenAny(t, timeout);
if (completedTask != t)
{
cts.Cancel();
await t;
}
That's what I have by now and it is working, though it is also verbose.
var cts = new CancellationTokenSource();
var t = Task.Run(async () => {
await t1();
await t2();
await t3();
await t4();
}, cts.Token);
cts.CancelAfter(TimeSpan.FromSeconds(4));
try
{
await t;
}
catch (OperationCanceledException)
{
// The cancellation token was triggered, add logic for that
....
}
Your original code is correct-ish -- it assumes that you always want the individual tasks to run to completion and that if cancelled you want the overall task to complete with success. Neither of these things are idiomatic.
A more normal way to do it would be something like:
var cts = new CancellationTokenSource();
var cancellationToken = cts.Token;
var t = Task.Run(async () => {
cancellationToken.ThrowIfCancellationRequested();
await t1(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
await t2(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
await t3(cancellationToken);
cancellationToken.ThrowIfCancellationRequested();
await t4(cancellationToken);
}, cancellationToken);
Then, somewhere else:
cts.Cancel();
You can omit the calls to ThrowIfCancellationRequested here assuming that the individual tasks check it fairly soon after entry, but the core idea is that you should be passing the token down to the innermost loop of whatever is doing the work, and they should throw on cancellation by calling that, which ends up setting the task to a cancelled state rather than a success state.
(So really you only need to actually call ThrowIfCancellationRequested if you hit a function that doesn't accept a CancellationToken parameter -- which is why all async methods should do so, as otherwise its task will be non-cancellable.)
It seems like your goal is to just stop execution if the whole operation has taken longer than 4 seconds.
If you were passing the CancellationToken to your t1/t2/etc. methods, I'd say you couldn't do any better than what you have. But as you have it, you could just use a Stopwatch instead of a CancellationToken:
var timeout = TimeSpan.FromSeconds(4);
var stopwatch = new Stopwatch();
stopwatch.Start();
await t1();
if (stopwatch.Elapsed > timeout) return;
await t2();
if (stopwatch.Elapsed > timeout) return;
await t3();
if (stopwatch.Elapsed > timeout) return;
await t4();
stopwatch.Stop();
I assume this is in a method somewhere, where you can use return, but you can modify if needed (return a value, throw an exception, etc.).
Your code is functionally OK, but when reading it at a glance it's not clear what it's doing. So I suggest that you encapsulate this logic inside a utility method with descriptive name and parameters:
public static async Task RunSequentially(IEnumerable<Func<Task>> taskFactories,
int timeout = Timeout.Infinite, bool onTimeoutAwaitIncompleteTask = false)
{
using (var cts = new CancellationTokenSource(timeout))
{
if (onTimeoutAwaitIncompleteTask)
{
await Task.Run(async () =>
{
foreach (var taskFactory in taskFactories)
{
if (cts.IsCancellationRequested) throw new TimeoutException();
await taskFactory();
}
});
}
else // On timeout return immediately
{
var allSequentially = Task.Run(async () =>
{
foreach (var taskFactory in taskFactories)
{
cts.Token.ThrowIfCancellationRequested();
var task = taskFactory(); // Synchronous part of task
cts.Token.ThrowIfCancellationRequested();
await task; // Asynchronous part of task
}
}, cts.Token);
var timeoutTask = new Task(() => {}, cts.Token);
var completedTask = await Task.WhenAny(allSequentially, timeoutTask);
if (completedTask.IsCanceled) throw new TimeoutException();
await completedTask; // Propagate any exception
}
}
}
This code defers from yours in that it throws a TimeoutException on time-out. I think that it's better to force the caller to handle explicitly this exception, instead of hiding the fact that the operation timed-out. The caller can ignore the exception by leaving empty the catch block:
try
{
await RunSequentially(new[] { t1, t2, t3, t4 },
timeout: 4000,
onTimeoutAwaitIncompleteTask: true);
}
catch (TimeoutException)
{
// Do nothing
}
You should consider using Microsoft's Reactive Framework (aka Rx) - NuGet System.Reactive and add using System.Reactive.Linq; - then you can do this:
IObservable<Unit> tasks =
from x1 in Observable.FromAsync(() => t1())
from x2 in Observable.FromAsync(() => t2())
from x3 in Observable.FromAsync(() => t3())
from x4 in Observable.FromAsync(() => t4())
select Unit.Default;
IObservable<Unit> timer =
Observable
.Timer(TimeSpan.FromSeconds(4.0))
.Select(x => Unit.Default)
IDisposable subscription =
Observable
.Amb(tasks, timer)
.Subscribe();
If the timer observable fires before the tasks are complete then the entire pipeline is canceled. No unnecessary tasks are run.
If you want to cancel manually then just call subscription.Dispose().
The code is simple and beautiful too.
Since no one here gave a better answer I'll do it myself.
The code I provided in the question is, in my opinion, most cohesive and also general enough to be recycled in other cases.

C# How to handle cancel task with eventhandler inside

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.

C# Task Ignoring Cancellation timeout

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.

Disposing CancellationTokenRegistrations

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();
}
}

Cancel all but last task

How do I cancel all but the last/latest task? For example, suppose I have a time-consuming task that is triggered on a button click. I only want the task from the last button click to run and the previous ones to cancel. Can you show me how this is normally done?
My attempt involves storing all tasks in a list along with their cancellation token and removing them when either the task completes or is cancelled. Creating a list to store both Task and CancellationToken seems like I'm doing too much for what I would've thought is a common requirement (eg. a User does a search for something and clicks the search button multiple times. Shouldn't only the last search be made and all other cancelled?). This is a common scenario, so I'd like to know how this is normally done. What is best practice here?
async void DoStuffAsync()
{
// Store tasks in a list
if (tasksAndCancelTokens == null)
tasksAndCancelTokens = new List<Tuple<Task, CancellationTokenSource>>();
else // we have a new Get request so cancel any previous
tasksAndCancelTokens.ForEach(t => t.Item2.Cancel());
// Create Cancellation Token
var cts = new CancellationTokenSource();
// Method to run asynchonously
Func<int> taskAction = () =>
{
// Something time consuming
Thread.Sleep(5000);
if (cts.Token.IsCancellationRequested)
cts.Token.ThrowIfCancellationRequested();
return 100;
};
// Create Task
Task<int> task = Task<int>.Factory.StartNew(taskAction, cts.Token);
// Create Tuple to store task in list
var tup = new Tuple<Task, CancellationTokenSource>(task, cts);
tasksAndCancelTokens.Add(tup);
try
{
int i = await task;
}
catch (OperationCanceledException)
{
// Don't need to do anything
}
finally
{
tasksAndCancelTokens.Remove(tup);
}
}
Thanks
If you just want to cancel the last task, then that's all you have to do:
CancellationTokenSource cts = null;
async void Button1_Click(...)
{
// Cancel the last task, if any.
if (cts != null)
cts.Cancel();
// Create Cancellation Token for this task.
cts = new CancellationTokenSource();
var token = cts.Token;
// Method to run asynchonously
Func<int> taskAction = () =>
{
// Something time consuming
Thread.Sleep(5000);
token.ThrowIfCancellationRequested();
return 100;
};
try
{
int i = await Task.Run(taskAction);
}
catch (OperationCanceledException)
{
// Don't need to do anything
return;
}
}

Categories