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:
- await foreach (var i in GetData(token)) ... where the consumer calls the async-iterator method,
- 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
C# Covariance on Subclass Return Types
How to Register Windows Forms with Simple Injector
How to Read the Current Path of |Datadirectory| from Config Settings
Wpf Clickonce Dpi Awareness Per-Monitor V2
Ef4 Cause Circular Reference in Web Service
Synchronizationlockexception on Monitor.Exit When Using Await
Piping in a File on the Command-Line Using System.Diagnostics.Process
Error "This Stream Does Not Support Seek Operations" in C#
How to Get Modified Date from File in C# on Windows Mobile
What's the Quickest Way to Compute Log2 of an Integer in C#
Using System.Io.Packaging to Generate a Zip File
Selecting the Size of a System.Drawing.Icon
Process.Start to Open an Url, Getting an Exception
C# Form.Transparencykey Working Different for Different Colors, Why
How to Handle Forms Authentication Timeout Exceptions in ASP.NET