What Is the Correct Way to Cancel an Async Operation That Doesn't Accept a Cancellationtoken

What is the correct way to cancel an async operation that doesn't accept a CancellationToken?

Assuming that you don't want to call the Stop method on the TcpListener class, there's no perfect solution here.

If you're alright with being notified when the operation doesn't complete within a certain time frame, but allowing the original operation to complete, then you can create an extension method, like so:

public static async Task<T> WithWaitCancellation<T>( 
this Task<T> task, CancellationToken cancellationToken)
{
// The tasck completion source.
var tcs = new TaskCompletionSource<bool>();

// Register with the cancellation token.
using(cancellationToken.Register( s => ((TaskCompletionSource<bool>)s).TrySetResult(true), tcs) )
{
// If the task waited on is the cancellation token...
if (task != await Task.WhenAny(task, tcs.Task))
throw new OperationCanceledException(cancellationToken);
}

// Wait for one or the other to complete.
return await task;
}

The above is from Stephen Toub's blog post "How do I cancel non-cancelable async operations?".

The caveat here bears repeating, this doesn't actually cancel the operation, because there is not an overload of the AcceptTcpClientAsync method that takes a CancellationToken, it's not able to be cancelled.

That means that if the extension method indicates that a cancellation did happen, you are cancelling the wait on the callback of the original Task, not cancelling the operation itself.

To that end, that is why I've renamed the method from WithCancellation to WithWaitCancellation to indicate that you are cancelling the wait, not the actual action.

From there, it's easy to use in your code:

// Create the listener.
var tcpListener = new TcpListener(connection);

// Start.
tcpListener.Start();

// The CancellationToken.
var cancellationToken = ...;

// Have to wait on an OperationCanceledException
// to see if it was cancelled.
try
{
// Wait for the client, with the ability to cancel
// the *wait*.
var client = await tcpListener.AcceptTcpClientAsync().
WithWaitCancellation(cancellationToken);
}
catch (AggregateException ae)
{
// Async exceptions are wrapped in
// an AggregateException, so you have to
// look here as well.
}
catch (OperationCancelledException oce)
{
// The operation was cancelled, branch
// code here.
}

Note that you'll have to wrap the call for your client to capture the OperationCanceledException instance thrown if the wait is cancelled.

I've also thrown in an AggregateException catch as exceptions are wrapped when thrown from asynchronous operations (you should test for yourself in this case).

That leaves the question of which approach is a better approach in the face of having a method like the Stop method (basically, anything which violently tears everything down, regardless of what is going on), which of course, depends on your circumstances.

If you are not sharing the resource that you're waiting on (in this case, the TcpListener), then it would probably be a better use of resources to call the abort method and swallow any exceptions that come from operations you're waiting on (you'll have to flip a bit when you call stop and monitor that bit in the other areas you're waiting on an operation). This adds some complexity to the code but if you're concerned about resource utilization and cleaning up as soon as possible, and this choice is available to you, then this is the way to go.

If resource utilization is not an issue and you're comfortable with a more cooperative mechanism, and you're not sharing the resource, then using the WithWaitCancellation method is fine. The pros here are that it's cleaner code, and easier to maintain.

What is the correct way to pass a cancellation token to an async stream?

According to the specification:

There are two main consumption scenarios:

  1. await foreach (var i in GetData(token)) ... where the consumer calls the async-iterator method,
  2. await foreach (var i in givenIAsyncEnumerable.WithCancellation(token)) ... where the consumer deals with a given IAsyncEnumerable instance.

You're calling GetFlibbityStream method, so this is the case #1. You should pass CancellationToken directly to the method and should not chain GetFlibbityStream with WithCancellation. Otherwise rule analyzer for CA2016 will emit warning, and it will be right.

WithCancellation is intended for the case #2. For example, there is some library type with property or method, which returns IAsyncEnumerable<T> and does not allow to pass CancellationToken directly.

Like this one:

public interface IFlibbityService
{
IAsyncEnumerable<aThingo> FlibbityStream { get; }
}

This still supports cancellation, but the only way to pass token to IFlibbityService.FlibbityStream is to use WithCancellation:

await foreach(var thng in flibbityService.FlibbityStream.WithCancellation(cancellationToken))
{
// ...
}

Back to your code, just throw away WithCancellation and pass token directly:

await foreach(var thng in ThingStreamCreator.GetFlibbityStream(cancellationToken))
{
}

Cancel Async operation

Everything that is awaited will need to have visibility on that CancellationToken. If it is a custom Task that you wrote, it should accept it as an argument, and periodically within the function, check to see if it has been cancelled. If so, it should take any actions needed (if any) to stop or rollback the operation in progress, then call ThrowIfCancellationRequested().

In your example code, you propbably want to pass token in to LoadDataFromDatabase and ApplyDataToBindings, as well as any children of those tasks.

There may be some more advanced situations where you don't want to pass the same CancellationToken in to child tasks, but you still want them to be cancellable. In those cases, you should create a new, internal to the Task, Cancellationtoken that you use for child tasks.

An important thing to remember is that ThrowIfCancellationRequested marks safe places within the Task that the Task can be stopped. There is no guaranteed safe way for the runtime to automatically detect safe places. If the Task were to automatically cancel its self as soon as cancellation was requested, it could potentially be left in an unknown state, so it is up to developers to mark those safe locations. It isn't uncommon to have several calls to check cancellation scattered throughout your Task.


I've just notice that your TriggerWeekChanged function is async void. This is usually considered an anti-pattern when it is used on something that is not an event handler. It can cause a lot of problems with tracking the completed status of the async operations within the method, and handling any exceptions that might be thrown from within it. You should be very weary of anything that is marked as async void that isn't an event handler, as it is the wrong thing to do 99%, or more, of the time. I would strongly recommend changing that to async Task, and consider passing in the CancellationToken from your other code.

Async methods that do not need cancellation

What is fundamental difference between asynchronous operations that expose CancellationToken in their signatures and ones that don't?

Asynchronous operations that expose CancellationToken in their signatures:

  • Can be cancelled

Asynchronous operations that don't expose CancellationToken in their signatures:

  • Can't be cancelled; or
  • they're cancellable some other way (e.g. yourAsyncObject.Dispose wraps up everything nicely)

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

What's the correct way to cancel an operation with a callback on a disposable object?

Is this correct and safe?

Yes.

What's the standard solution for using a cancellation token to cancel an async operation on a disposable object where the operation only supports cancellation through registering a callback?

Ideally, operations take a CancellationToken directly. In this case, the approach you already have is fine.

How to chain async tasks with a CancellationToken?

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.

Async/await with CancellationToken doesn't cancel the operation

I want to use the CancellationToken to abort a file download

Downloading a file is an I/O operation, for which asynchronous cancelable (I/O completion port based) functions are available on the .NET platform. Yet you seem to not be using them.

Instead you appear to be creating (a chain of) tasks using Task.Run that perform blocking I/O, where a cancelation token is not passed on to each task in your Task.Run chain.

For examples of doing async, awaitable and cancelable file downloads, refer to:

  • Using HttpClient: How to copy HttpContent async and cancelable?
  • Windows Phone:
    Downloading and saving a file Async in Windows Phone 8
  • Using WebClient: Has its own cancellation mechanism: the CancelAsync method, you can connect it to your cancellation token, using the token's Register method:

    myToken.Register(myWebclient.CancelAsync);
  • Using the abstract WebRequest: If it was not created using an attached cancelation token, as seems to be the case for your edited example, and you are not actually downloading a file, but reading a content string, you need to use a combination of a few of the earlier mentioned methods.

You can do the following:

static async Task<string> WsGetResponseString(WebRequest webreq, CancellationToken cancelToken)`
{
cancelToken.Register(webreq.Abort);
using (var response = await webReq.GetResponseAsync())
using (var stream = response.GetResponseStream())
using (var destStream = new MemoryStream())
{
await stream.CopyToAsync(destStream, 4096, cancelToken);
return Encoding.UTF8.GetString(destStream.ToArray());
}
}

Correctly cancelling and clearing a CancellationToken

Should we be synchronising access to the cancellationTokenSource? Because two of the four providers are so quick they never deal with the cancellationTokenSource.

You should synchronize access to the CTS, but not for that reason. The reason is because the quickEntryCancellationTokenSource field can be accessed from multiple threads.

If this is in the context of a UI thread, then you can just use the UI thread for synchronization:

// No ConfigureAwait(false); we want to only access the CTS from the UI thread.
await Task.WhenAll(getThings1Task, getThings2Task, getThings3Task, getThings4Task);
...
this.quickEntryCancellationTokenSource = null;

Also, I would consider passing the CT in to getThings1Task and the others, instead of having them read from a private variable.

Should all four methods be calling ThrowIfCancellationRequested?

That's up to you. If your two "fast" provider methods don't take a CT, then you could check it, but it doesn't sound to me like it would provide any benefit.

Is it wrong that two of the four provider methods never await anything?

If they're doing I/O, they should be using await, even if they normally are very fast. If they're not doing I/O, then I don't see much point in wrapping them in a Task at all. Presumably, the current code executes synchronously and always returns a completed task, so it's just a more complex way of just executing code synchronously in the first place. OTOH, if they're doing I/O but you don't have an asynchronous API for them (and since you're in a UI context), you can wrap them in a Task.Run.

Once we have read the .Result of each of the Task

Consider using await, even for completed tasks. It'll make your exception handling code easier.



Related Topics



Leave a reply



Submit