How to Cancel a Task in Await

How to cancel a Task in await?

Read up on Cancellation (which was introduced in .NET 4.0 and is largely unchanged since then) and the Task-Based Asynchronous Pattern, which provides guidelines on how to use CancellationToken with async methods.

To summarize, you pass a CancellationToken into each method that supports cancellation, and that method must check it periodically.

private async Task TryTask()
{
CancellationTokenSource source = new CancellationTokenSource();
source.CancelAfter(TimeSpan.FromSeconds(1));
Task<int> task = Task.Run(() => slowFunc(1, 2, source.Token), source.Token);

// (A canceled task will raise an exception when awaited).
await task;
}

private int slowFunc(int a, int b, CancellationToken cancellationToken)
{
string someString = string.Empty;
for (int i = 0; i < 200000; i++)
{
someString += "a";
if (i % 1000 == 0)
cancellationToken.ThrowIfCancellationRequested();
}

return a + b;
}

Cancel Task in async void

Technically, you can put it like this, but look: you fire and forget, let return Task for caller to know if Subf2mCore has been completed, failed or cancelled:

private async Task Subf2mCore(CancellationToken ct)
{
HtmlDocument doc = await web.LoadFromWebAsync(url);
...
foreach (var node in doc)
{
// Often we cancel by throwing exception:
// it's easy to detect that the task is cancelled by catching this exception
// ct.ThrowIfCancellationRequested();

// You prefer to cancel manually:
// your cancellation can be silent (no exceptions) but it'll be
// difficult for caller to detect if task completed or not
// (partially completed and cancelled)
if (!ct.IsCancellationRequested)
{
....
}
}
}

// If we don't want to cancel
private async Task Subf2mCore() => Subf2mCore(CancellationToken.None);

Usage: do not forget to Dispose CancellationTokenSource instance:

using (CancellationTokenSource ts = new CancellationTokenSource()) {
...
await Subf2mCore(ts.Token);
...
}

Edit: if you want to cancel from outside:

private CancellationTokenSource ts = null;

...

using (CancellationTokenSource _ts = new CancellationTokenSource()) {
// previous task (if any) cancellation
if (null != ts)
ts.Cancel();

// let cancel from outside
ts = _ts;

try {
...
await Subf2mCore(_ts.Token);
...
}
finally {
// task completed, we can't cancel it any more
ts = null;
}
}

How to cancel await Task that perform multiple task in C#

I think you can create the CancellationTokenSource from the method which you are calling PollCurrentHardwareStatus(). Please check bellow example:

Add CancellationTokenSource as a parameter in PollCurrentHardwareStatus method

public static async Task PollCurrentHardwareStatus(CancellationToken cts)
{
// your logic code
// ...............
}

Create a CancellationTokenSource and call it on your Page class:

public class Page
{
private CancellationTokenSource cancellationTokenSource;

public Page()
{
cancellationTokenSource = new CancellationTokenSource();
}

public async void CallPoll()
{
await PollCurrentHardwareStatus(cancellationTokenSource.Token);
}

public void OnCancelPoll(object sender, EventArgs e)
{
cancellationTokenSource.Cancel();
}
}

Concise way to await a canceled Task?

I don't think there is anything built-in, but you could capture your logic in extension methods (one for Task, one for Task<T>):

public static async Task IgnoreWhenCancelled(this Task task)
{
try
{
await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
}
}

public static async Task<T> IgnoreWhenCancelled<T>(this Task<T> task)
{
try
{
return await task.ConfigureAwait(false);
}
catch (OperationCanceledException)
{
return default;
}
}

Then you can write your code simpler:

await task.IgnoreWhenCancelled();

or

var result = await task.IgnoreWhenCancelled();

(You might still want to add .ConfigureAwait(false) depending on your synchronization needs.)

How to properly cancel Swift async/await function

There are two basic patterns for the implementation of our own cancelation logic:

  1. Use withTaskCancellationHandler(operation:onCancel:) to wrap your cancelable asynchronous process.

    This is useful when calling a cancelable legacy API and wrapping it in a Task. This way, canceling a task can proactively stop the asynchronous process in your legacy API, rather than waiting until you reach a manual isCancelled or checkCancellation call. This pattern works well with iOS 13/14 URLSession API, or any asynchronous API that offers a cancelation method.

  2. Periodically check isCancelled or try checkCancellation.

    This is useful in scenarios where you are performing some manual, computationally intensive process with a loop.

    Many discussions about handling cooperative cancelation tend to dwell on these methods, but when dealing with legacy cancelable API, the aforementioned withTaskCancellationHandler is generally the better solution.

So, I would personally focus on implementing cooperative cancelation in your methods that wrap some legacy asynchronous process. And generally the cancelation logic will percolate up, frequently not requiring additional checking further up in the call chain, often handled by whatever error handling logic you might already have.

How can I create a Task that can cancel itself and another Task if needed?

After a lot of searching, I came across "A pattern for self-cancelling and restarting task". This was exactly what I needed, and after some tweaks, I can safely say I got what I wanted. My implementation goes as follows:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

/// <summary>
/// The task that is currently pending.
/// </summary>
private Task _pendingTask = null;

/// <summary>
/// A linked token source to control Task execution.
/// </summary>
private CancellationTokenSource _tokenSource = null;

/// <summary>
/// Does some serious work.
/// </summary>
/// <exception cref="OperationCanceledException">Thrown when the
/// operation is cancelled.</exception>
public async Task SeriousWorkAsync(CancellationToken token)
{
await CompletePendingAsync(token);
this._pendingTask = SeriousImpl(this._tokenSource.Token);
await this._pendingTask;
}

/// <summary>
/// Does some fun work.
/// </summary>
/// <exception cref="OperationCanceledException">Thrown when the
/// operation is cancelled.</exception>
public async Task FunWorkAsync(CancellationToken token)
{
await CompletePendingAsync(token);
this._pendingTask = FunImpl(this._tokenSource.Token);
await this._pendingTask;
}

/// <summary>
/// Cancels the pending Task and waits for it to complete.
/// </summary>
/// <exception cref="OperationCanceledException">If the new token has
/// been canceled before the Task, an exception is thrown.</exception>
private async Task CompletePendingAsync(CancellationToken token)
{
// Generate a new linked token
var previousCts = this._tokenSource;
var newCts = CancellationTokenSource.CreateLinkedTokenSource(token);
this._tokenSource = newCts;

if (previousCts != null)
{
// Cancel the previous session and wait for its termination
previousCts.Cancel();
try { await this._pendingTask; } catch { }
}

// We need to check if we've been canceled
newCts.Token.ThrowIfCancellationRequested();
}

Ideally, calling the methods would look like this:

try
{
await SeriousWorkAsync(new CancellationToken());
}
catch (OperationCanceledException) { }

If you prefer, you can wrap your methods inside a try catch and always generate a new token, so consumers wouldn't need to apply special handling for cancellation:

var token = new CancellationToken();
try
{
await CompletePendingAsync(token);
this._pendingTask = FunImpl(this._tokenSource.Token);
await this._pendingTask;
}
catch { }

Lastly, I tested using the following implementations for SeriousWorkAsync and FunWorkAsync:

private async Task SeriousImpl(CancellationToken token)
{
Debug.WriteLine("--- Doing serious stuff ---");
for (int i = 1000; i <= 4000; i += 1000)
{
token.ThrowIfCancellationRequested();
Debug.WriteLine("Sending mails for " + i + "ms...");
await Task.Delay(i);
}
Debug.WriteLine("--- Done! ---");
}

private async Task FunImpl(CancellationToken token)
{
Debug.WriteLine("--- Having fun! ---");
for (int i = 1000; i <= 4000; i += 1000)
{
token.ThrowIfCancellationRequested();
Debug.WriteLine("Laughing for " + i + "ms...");
await Task.Delay(i);
}
Debug.WriteLine("--- Done! ---");
}

How to restart a async method? Cancel previous run, await it and then start it

I think the problem is inside RestartAsync method. Beware that an async method will immediately return a task if it's going to await something, so second RestartAsync actually return before it swap its task then third RestartAsync comes in and awaiting the task first RestartAsync.

Also if RestartAsync is going to be executed by multiple thread, you may want to wrap _cts and _somethingIsRunningTask into one and swap values with Interlocked.Exchange method to prevent race condition.

Here is my example code, not fully tested:

public class Program
{
static async Task Main(string[] args)
{
RestartTaskDemo restartTaskDemo = new RestartTaskDemo();

Task[] tasks = { restartTaskDemo.RestartAsync( 1000 ), restartTaskDemo.RestartAsync( 1000 ), restartTaskDemo.RestartAsync( 1000 ) };
await Task.WhenAll( tasks );

Console.ReadLine();
}
}

public class RestartTaskDemo
{
private int Counter = 0;

private TaskEntry PreviousTask = new TaskEntry( Task.CompletedTask, new CancellationTokenSource() );

public async Task RestartAsync( int delay )
{
TaskCompletionSource<bool> taskCompletionSource = new TaskCompletionSource<bool>();
CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();

TaskEntry previousTaskEntry = Interlocked.Exchange( ref PreviousTask, new TaskEntry( taskCompletionSource.Task, cancellationTokenSource ) );

previousTaskEntry.CancellationTokenSource.Cancel();
await previousTaskEntry.Task.ContinueWith( Continue );

async Task Continue( Task previousTask )
{
try
{
await DoworkAsync( delay, cancellationTokenSource.Token );
taskCompletionSource.TrySetResult( true );
}
catch( TaskCanceledException )
{
taskCompletionSource.TrySetCanceled();
}
}
}

private async Task DoworkAsync( int delay, CancellationToken cancellationToken )
{
int count = Interlocked.Increment( ref Counter );
Console.WriteLine( $"Task {count} started." );

try
{
await Task.Delay( delay, cancellationToken );
Console.WriteLine( $"Task {count} finished." );
}
catch( TaskCanceledException )
{
Console.WriteLine( $"Task {count} cancelled." );
throw;
}
}

private class TaskEntry
{
public Task Task { get; }

public CancellationTokenSource CancellationTokenSource { get; }

public TaskEntry( Task task, CancellationTokenSource cancellationTokenSource )
{
Task = task;
CancellationTokenSource = cancellationTokenSource;
}
}
}

How to make asyncio cancel() to actually cancel the task?

self.task.cancel() marks the task as cancelled, but at that moment is this the active task on CPU. A task switch must occur to allow the scheduler to process the cancellation.

From the cancel() docs:

Request the Task to be cancelled.

This arranges for a CancelledError exception to be thrown into the
wrapped coroutine on the next cycle of the event loop.

I have inserted an unmocked await asyncio.sleep(0) to ensure the needed task switch, now it doesn't loop any more:

realsleep = asyncio.sleep

class AsyncSleepMock(AsyncMock):
def __init__(self):
super(AsyncMock, self).__init__()
self.task = None

async def __call__(self, delay, *args, **kwargs):
self.task.cancel()
await realsleep(0)
return await super(AsyncMock, self).__call__(delay, *args, **kwargs)

For completness, I'm adding a quote from the asyncio.sleep() description:

sleep() always suspends the current task, allowing other tasks to run.

Setting the delay to 0 provides an optimized path to allow other tasks
to run. This can be used by long-running functions to avoid blocking
the event loop for the full duration of the function call.




Related Topics



Leave a reply



Submit