How to fix the inconsistent cancellation behavior of Task.Run? - c#

The Task.Run method has overloads that accept both sync and async delegates:
public static Task Run (Action action);
public static Task Run (Func<Task> function);
Unfortunately these overloads don't behave the same when the delegate throws an OperationCanceledException. The sync delegate results in a Faulted task, and the async delegate results in a Canceled task. Here is a minimal demonstration of this behavior:
CancellationToken token = new(canceled: true);
Task taskSync = Task.Run(() => token.ThrowIfCancellationRequested());
Task taskAsync = Task.Run(async () => token.ThrowIfCancellationRequested());
try { Task.WaitAll(taskSync, taskAsync); } catch { }
Console.WriteLine($"taskSync.Status: {taskSync.Status}"); // Faulted
Console.WriteLine($"taskAsync.Status: {taskAsync.Status}"); // Canceled (undesirable)
Output:
taskSync.Status: Faulted
taskAsync.Status: Canceled
Online demo.
This inconsistency has been observed also in this question:
Faulted vs Canceled task status after CancellationToken.ThrowIfCancellationRequested
That question asks about the "why". My question here is how to fix it. Specifically my question is: How to implement a variant of the Task.Run with async delegate, that behaves like the built-in Task.Run with sync delegate? In case of an OperationCanceledException it should complete asynchronously as Faulted, except when the supplied cancellationToken argument matches the token stored in the exception, in which case it should complete as Canceled.
public static Task TaskRun2 (Func<Task> action,
CancellationToken cancellationToken = default);
Here is some code with the desirable behavior of the requested method:
CancellationToken token = new(canceled: true);
Task taskA = TaskRun2(async () => token.ThrowIfCancellationRequested());
Task taskB = TaskRun2(async () => token.ThrowIfCancellationRequested(), token);
try { Task.WaitAll(taskA, taskB); } catch { }
Console.WriteLine($"taskA.Status: {taskA.Status}");
Console.WriteLine($"taskB.Status: {taskB.Status}");
Desirable output:
taskA.Status: Faulted
taskB.Status: Canceled

One way to solve this problem is to use the existing Task.Run method, and attach a ContinueWith continuation that alters its cancellation behavior. The continuation returns either the original task or a faulted task, so it returns a Task<Task>. Eventually this nested task is unwrapped to a simple Task, with the help of the Unwrap method:
/// <summary>
/// Queues the specified work to run on the thread pool, and returns a proxy
/// for the task returned by the action. The cancellation behavior is the same
/// with the Task.Run(Action, CancellationToken) method.
/// </summary>
public static Task TaskRun2 (Func<Task> action,
CancellationToken cancellationToken = default)
{
ArgumentNullException.ThrowIfNull(action);
return Task.Run(action, cancellationToken).ContinueWith(t =>
{
if (t.IsCanceled)
{
CancellationToken taskToken = new TaskCanceledException(t).CancellationToken;
if (taskToken != cancellationToken)
return Task.FromException(new OperationCanceledException(taskToken));
}
return t;
}, CancellationToken.None, TaskContinuationOptions.DenyChildAttach |
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default).Unwrap();
}
Online demo.
The TaskCanceledException constructor is used in order to get access to the CancellationToken stored inside the canceled Task. Unfortunately the Task.CancellationToken property is not exposed (it is internal), but fortunately this workaround exists.

Related

C#: How to correctly execute auto-generated methods from web service with a timeout in an async method?

I need to call some methods from a web service, specified in WSDL. VisualStudio created the corresponding methods for me, including async variants. The documentation specifies (different) timeouts for those methods, some of them could take several minutes. So what is the best way to implement this?
I have two approaches, but I'm not sure which one is better, or if I should do something completely different:
Variant 1: use generated async method and task.Wait instead of await:
public async Task<ResultType> MyMethod1Async()
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(60000);
Task<ResultType> task = MyWebService.getSomeObjectAsync();
task.Wait(cts.Token);
return task.Result;
}
Variant 2: execute generated synchronous method in Task.Run:
public async Task<ResultType> MyMethod2Async()
{
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(60000);
Task<ResultType> task = Task.Run(
() => MyWebService.getSomeObject(),
cts.Token);
return await task;
}
Neither option will do what you want.
Variant 1 will block on task.Result regardless of any timeout. Variant 2 will not be cancelled once the method has started running
If the async task does not support cancellation, the best you can do is to return to the caller when the timeout is reached, and let the task continue in the background, any eventual result will be ignored. For example:
public async Task<ResultType> MyMethodAsync<T>(TimeSpan timeout)
{
var task = SomeAsyncMethod<ResultType>();
var timeoutTask = Task.Delay(timeout);
var completedTask = await Task.WhenAny(task, timeoutTask);
if (completedTask == timeoutTask)
{
// Handle timeout somehow
throw new TimeoutException("...");
}
return task.Result;
}
This is obviously not appropriate for compute bound tasks, and if it is possible to use real support for cancellation it should be used.

Is there a way I can cause a running method to stop immediately with a cts.Cancel();

I have code that creates a CancellationTokenSource and that passes it to a method.
I have code in another are of the app that issues a cts.Cancel();
Is there a way that I can cause that method to stop immediately without me having to wait for the two lines inside the while loop to finish?
Note that I would be okay if it caused an exception that I could handle.
public async Task OnAppearing()
{
cts = new CancellationTokenSource();
await GetCards(cts.Token);
}
public async Task GetCards(CancellationToken ct)
{
while (!ct.IsCancellationRequested)
{
App.viewablePhrases = App.DB.GetViewablePhrases(Settings.Mode, Settings.Pts);
await CheckAvailability();
}
}
What I can suggest:
Modify GetViewablePhrases and CheckAvailability so you could pass the CancellationToken to them;
Use ct.ThrowIfCancellationRequested() inside these functions;
Try / Catch the OperationCanceledException inside GetCards;
As for your your functions I don't know how exactly they work inside. But let's assume you have a long running iteration inside one of them:
CheckAvailability(CancellationToken ct)
{
for(;;)
{
// if cts.Cancel() was executed - this method throws the OperationCanceledException
// if it wasn't the method does nothing
ct.ThrowIfCancellationRequested();
...calculations...
}
}
Or let's say you are going to access your database inside one of the function and you know that this process is going to take a while:
CheckAvailability(CancellationToken ct)
{
ct.ThrowIfCancellationRequested();
AccessingDatabase();
}
This will not only prevent your functions from proceeding with execution, this also will set the executioner Task status as TaskStatus.Canceled
And don't forget to catch the Exception:
public async Task GetCards(CancellationToken ct)
{
try
{
App.viewablePhrases = App.DB.GetViewablePhrases(Settings.Mode, Settings.Pts, ct);
await CheckAvailability(ct);
}
catch(OperationCanceledException ex)
{
// handle the cancelation...
}
catch
{
// handle the unexpected exception
}
}
If you are OK with cancelling not the task, but the awaiting of the task, you could use a cancelable wrapper. In case of cancellation the underlying task will continue running, but the wrapper will complete immediately as canceled.
public static Task AsCancelable(this Task task,
CancellationToken cancellationToken)
{
var cancelable = new Task(() => { }, cancellationToken);
return Task.WhenAny(task, cancelable).Unwrap();
}
public static Task<T> AsCancelable<T>(this Task<T> task,
CancellationToken cancellationToken)
{
var cancelable = new Task<T>(() => default, cancellationToken);
return Task.WhenAny(task, cancelable).Unwrap();
}
Usage example:
await GetCards(cts.Token).AsCancelable(cts.Token);
This extension method can also be implemented using a TaskCompletionSource<T> (instead of the Task<T> constructor).

Cancel long running operation on client cancel

I started my first project with asp.net core and razor pages.
On a client request, a long running database operation will be started.
Now i want to recognize, when the user leaves the website, so the database operation can be cancelled.
I've tried it with a cancellationToken, but it will never be cancelled.
public async Task<JsonResult> OnPostReadAsync([DataSourceRequest] DataSourceRequest request, CancellationToken cancellationToken)
{
var messages = await _logMessageService.GetLogMessagesAsync(request, cancellationToken);
return new JsonResult(messages.ToDataSourceResult(request));
}
The function get called by an Telerik Kendo UI Grid.
Can you tell me, why the cancellation token don't get cancelled, or what other options I have to detect a abortion by the client?
Edit 1
I pass the Token through to this function call of a NpgsqlCommand:
var dataReader = await command.ExecuteReaderAsync(cancellationToken);
To cancel IO bound i.e. task which is running long , following is code that you can do which i got form the C# with CLR book.
Design extension method for task as below.
private static async Task<TResult> WithCancellation<TResult>(this Task<TResult> originalTask,
CancellationToken ct) {
// Create a Task that completes when the CancellationToken is canceled
var cancelTask = new TaskCompletionSource<Void>();
// When the CancellationToken is canceled, complete the Task
using (ct.Register(
t => ((TaskCompletionSource<Void>)t).TrySetResult(new Void()), cancelTask)) {
// Create a Task that completes when either the original or
// CancellationToken Task completes
Task any = await Task.WhenAny(originalTask, cancelTask.Task);
// If any Task completes due to CancellationToken, throw OperationCanceledException
if (any == cancelTask.Task) ct.ThrowIfCancellationRequested();
}
// await original task (synchronously); if it failed, awaiting it
// throws 1st inner exception instead of AggregateException
return await originalTask;
}
as given in following example code you can cancel it by using extension method designed above.
public static async Task Go() {
// Create a CancellationTokenSource that cancels itself after # milliseconds
var cts = new CancellationTokenSource(5000); // To cancel sooner, call cts.Cancel()
var ct = cts.Token;
try {
// I used Task.Delay for testing; replace this with another method that returns a Task
await Task.Delay(10000).WithCancellation(ct);
Console.WriteLine("Task completed");
}
catch (OperationCanceledException) {
Console.WriteLine("Task cancelled");
}
}
In this example cancellation done based on given time , but you can call cancellation by calling cancel method.
So I found the answer myself after some more research.
The problem is a bug in IISExpress as mentioned here: https://github.com/aspnet/Mvc/issues/5239#issuecomment-323567952
I switched to Kestrel and now everything works as expected.

Should I use CancellationTokenSource or CancellationToken to Cancel a task in .NET

I see CancellationToken and CancellationTokenSource both have IsCancellationRequested getter method. Most examples pass CancellationToken to the method that is executed inside the Task. It looks to me by using any of these, call can return. If I use IsCancellationRequested of CancellationTokenSource, will it be a problem? When I should I throw exception (by using ThrowIfCancellationRequested) or just return from the method if there is cancellation request as shown in the following code?
class Program
{
//If CancellationToken is passed then it behaves in same way?
public static int TaskMethod(CancellationTokenSource tokenSource)
{
int tick = 0;
while (!tokenSource.IsCancellationRequested)
{
Console.Write('*');
Thread.Sleep(500);
tick++;
//token.Token.ThrowIfCancellationRequested();
}
//Should I just return or use ThrowIfCancellationRequested?
return tick;
}
public static void Main()
{
CancellationTokenSource tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
Task<int> task = Task.Factory.StartNew<int>(() => TaskMethod(tokenSource));
Console.WriteLine("Press enter to stop the task");
Console.ReadLine();
tokenSource.Cancel();
Console.WriteLine("{0}", task.Result);
}
}
CancellationTokenSource holds and controls CancellationToken instance. The task method is expected to use the cancellation token but not modify it. If you pass CancellationTokenSource to a method, the method gets full control of the cancellation token. So the method should take CancellationToken as a parameter.
The behavior of the method after cancellation request is up to you. There is no rule that the task should be in canceled state (throw TaskCanceledException) if it has been terminated by the cancellation token.
In my view, CancellationTokenSource is used to start the cancellation (for example, by a another/parent thread, ). CancellationToken.Token is the associated CancellationToken that you would pass to something like TaskFactory.StartNew() as MSDN says:
CancellationTokenSource.IsCancellationRequested property indicates whether
cancellation has been requested for this token source, such as due to a call to its Cancel method.
And then the Task would monitor the CancellationToken.IsCancellationRequested to determine when to shut down.
For property CancellationToken.IsCancellationRequested MSDN says:
Gets whether cancellation has been requested for this token.
To sum up, I would like to tell that CancellationTokenSource.IsCancellationRequested is used to associate CancellationToken with TaskFactory.StartNew(). And CancellationToken.IsCancellationRequested is used to define whether cancellation has been requested for this token. Moreover, IntelliSense has the same definition for CancellationTokenSource.Token.IsCancellationRequested and for CancellationToken.IsCancellationRequested.

How to await an awaitable object while observing a CancellationToken?

I have an awaitable object which is NOT a Task (for example, an IObservable<T> with RX installed). I want to create a Task, which will end up as cancelled if the supplied CancellationToken is cancelled or return the result of the awaitable object otherwise. I came up with the following code:
public static Task ObserveTokenAsync(CancellationToken token)
{
TaskCompletionSource<Unit> tcs = new TaskCompletionSource<Unit>();
token.Register(() => tcs.SetCanceled());
return tcs.Task;
}
public static async Task<T> WrapObservableAsync<T>(IObservable<T> obs)
{
return await obs;
}
public static async Task<T> AwaitWhileObservingTokenAsync<T>(IObservable<T> obs, CancellationToken token)
{
var obsTask = WrapObservableAsync(obs);
var tokenTask = ObserveTokenAsync(token);
await Task.WhenAny(obsTask, tokenTask).ConfigureAwait(false);
token.ThrowIfCancellationRequested();
return obsTask.Result;
}
With this approach I will need to overload/rewrite the last two methods for each of the awaitable types I will be using. Is there any better way to do this? My guess is that the INotifyCompletion interface may somehow be useful.
Also, can the ObserveTokenAsync method cause some kind of resource/memory leaks if the supplied tokens won't get cancelled? If yes, will setting the TaskCompletionSource as finished at the end of the AwaitWhileObservingTokenAsync method will be a good way to fix it?
Use the TaskObservableExtensions.ToTask extension method from Rx. It takes a CancellationToken and will take the last element from the observable when available to finish the returned task. When the CancellationToken is canceled the returned task will throw an OperationCanceledException when awaited. If the observable does not contain any elements, the returned task will throw an exception when awaited (probably an InvalidOperationException though I would have to look that up).

Categories