Task from cancellation token?
Asked Answered
S

2

24

Given a cancellation token, I'd like to create an awaitable task out of it, which is never complete but can be cancelled. I need it for a pattern like this, which IMO should be quite common:

async Task DoStuff(Task t, CancellationToken ct)
{
   // t was made from TaskCompletionSource, 
   // both t and ct are beyond my control

   Task t2 = TaskFromCancellationToken(ct);
   await Task.WhenAny(t, t2);

   // do stuff
}

The best idea I've got so far is this:

Task TaskFromCancelationToken(CancellationToken ct)
{
    return Task.Delay(Timeout.Infinite, ct);
}

Is there a better way to make this logic happen?

Sapsago answered 7/9, 2013 at 5:32 Comment(0)
C
30

It's not extremely common, but it's common enough to be part of my AsyncEx library. I use something like this:

public static Task AsTask(this CancellationToken cancellationToken)
{
    var tcs = new TaskCompletionSource<object>();
    cancellationToken.Register(() => tcs.TrySetCanceled(),
        useSynchronizationContext: false);
    return tcs.Task;
}

Update: These days, I recommend using something like CancellationTokenTaskSource, which properly handles all lifetimes with no possibility of resource leaks.

Chadchadabe answered 7/9, 2013 at 11:37 Comment(10)
But cancellationToken.Register returns CancellationTokenRegistration, which you never dispose of (it implements IDisposable). Isn't that a problem?Riordan
@Paya: Yes. The current version of AsyncEx introduces a ToCancellationTokenTaskSource method which is recommended instead of AsTask. The next version removes AsTask completely.Chadchadabe
Is there any reason to prefer that over the suggested Task.Delay? That one seems like a very elegant solution and does not come with the IDisposable "annoyance".Riordan
@Paya: You still have the same resource leak; it's just not as noticeable.Chadchadabe
Right. I guess there is no escaping IDisposable then. For the OP, maybe UntilCompletionOrCancellation wrapper might be suitable alternative, if there is no need to keep the cancellation token task around.Riordan
@Paya: That's the same approach that I take in the new version of AsyncEx, with WaitAsync and WhenAnyAsync extension methods.Chadchadabe
@Riordan in order not to leak the registration, all you have to do is dispose the CancellationTokenSource and it will dispose all its token's registrations.Giffer
Shouldn't you pass the cancellation token into TrySetCanceled?Sporocyte
@StriplingWarrior: Yes. At the time I wrote this answer, that overload didn't exist. The modern AsyncEx code does pass the CT.Chadchadabe
@StephenCleary, could you please mention and briefly describe your CancellationTokenTaskSource in the answer so that people were better informed?Archival
R
14
Task.Delay(Timeout.Infinite, cancellationToken)

The answer you suggest in your question is the best solution to my knowledge. Here's why:

  • It is safe
  • Very small piece of code required
  • Standard library

The Task.Delay approach is heavily used by a lot of folks as per my knowledge, and is also recommended on Microsoft blogs. MSDN Example.

Why write code (including tests) yourself leveraging TaskCompletionSource for converting a cancellation token to a task? It is preferable to leverage the standard libraries instead of reinventing the wheel; they are more likely to be bug free than your code.

Reinaldoreinaldos answered 9/3, 2018 at 2:55 Comment(6)
Be warned that this call will always raise an OperationCanceledException when done, see docs here: learn.microsoft.com/en-us/dotnet/api/…Calliper
Note that the timeout is Timeout.Infinite. Therefore, effectively, in practice, OperationCanceledException should never be called.Reinaldoreinaldos
OperationCanceledException will be raised when the cancellationToken transition to canceled stateCalliper
You are absolutely right. I somehow misread the cancellationToken as CancellationToken.None.Reinaldoreinaldos
I don't think this is safe, it's safe only when the cancellationToken is canceled but what if it's never cancelled? you will have a never-end delay task. Each time using this will spawn such a never-end task. I think that would cause some issue if used too much.Cramp
If the cancellation token is never cancelled then the task is ongoing by definition. However, if you feel more comfortable, you can provide an upper limit in terms of time to Task.Delay in addition to the cancellation token. I have tried other approaches of creating a task using cancellation token too but this approach is the easiest and least error-prone, and hence, I recommend it.Reinaldoreinaldos

© 2022 - 2024 — McMap. All rights reserved.