2

Suppose I want to ensure that a task object which is returned from an asynchronous method will transition into the canceled state, due to a cancellation request by the caller.

Catch is: this should occur regardless of how the asynchronous methods consumed by the aforementioned method are implemented and whether or not they ever complete.

Consider the following extension method:

    public static Task<T> ToTask<T>(this CancellationToken cancellationToken)
    {
        var tcs = new TaskCompletionSource<T>();
        cancellationToken.Register(() => { tcs.SetCanceled(); });
        return tcs.Task;
    }

I can now consume such a task to ensure said scenario:

 public async Task<Item> ProvideItemAsync(CancellationToken cancellationToken)
    {
        Task<Item> cancellationTask = cancellationToken.ToTask<Item>();

        Task<Item> itemTask = _itemProvider.ProvideItemAsync(cancellationToken);

        Task<Task<Item>> compoundTask = Task.WhenAny(cancellationTask, itemTask);

        Task<Item> finishedTask = await compoundTask;

        return await finishedTask;
    }

My questions are:

1) Are there any issues with this approach?
2) Is there a built in API to facilitate such a use case

Thanks!

Eyal Perry
  • 2,255
  • 18
  • 26
  • Is it `rawImageTask` or `itemTask`? `itemTask` is currently an unused local. – spender Oct 26 '16 at 13:04
  • @spender Sorry, that one escaped editing. – Eyal Perry Oct 26 '16 at 13:04
  • 2
    "Regardless of how the actual operation is implemented" is not possible. Task cancellation is a cooperative process; if the task doesn't check anywhere if it's been cancelled, it won't actually get cancelled. Of course, you're still free to ignore the result in that case and *say* it was cancelled -- but it'll keep on running in the background. – Jeroen Mostert Oct 26 '16 at 13:07
  • @JeroenMostert. You are correct, but what I can ensure is that the client's request for cancellation is NOT utterly ignored, which is sometimes crucial. I have revised my question to further illustrate my intentions. – Eyal Perry Oct 26 '16 at 13:09
  • 1
    There's a problem with your `ToTask` method that's common to any approach that uses `Register`; this leaks resources. See [this question](http://stackoverflow.com/questions/18670111/task-from-cancellation-token) for more information (and some proposed solutions). Including a link to [this](https://msdn.microsoft.com/en-us/library/hh873173(v=vs.110).aspx#code-snippet-25), which seems to implement you're after. – Jeroen Mostert Oct 26 '16 at 13:32

1 Answers1

4

Suppose I want to ensure the cancellation of an asynchronous operation, Regardless of how the actual operation is implemented and whether or not it completes.

That is not possible unless you wrap the code into a separate process.

When I say "ensure", I mean to say that the task denoting said operation transitions into the canceled state.

If you just want to cancel the task (and not the operation itself), then sure, you can do that.

Are there any issues with this approach?

There's some tricky edge cases here. In particular, you need to dispose the result of Register if the task completes successfully.

I recommend using the WaitAsync extension method in my AsyncEx.Tasks library:

public Task<Item> ProvideItemAsync(CancellationToken cancellationToken)
{
  return _itemProvider.ProvideItemAsync(cancellationToken).WaitAsync(cancellationToken);
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • 1
    Unfortunately I can not take on dependencies at that Assembly level. I will, however, understand your implementation and use this knowledge to solve the issue at hand. BTW, if I might say- Your work inspires a lot of ideas in my head. Thank you. – Eyal Perry Oct 26 '16 at 14:44
  • If I may ask, in your implementation of the DoWaitAsyncMethod- why did you invoke the 'ConfigureAwait' method with a false parameter? What does purpose does it serve? – Eyal Perry Oct 27 '16 at 08:27
  • 1
    @EyalPerry: I explain that in my [intro to `async` blog post](http://blog.stephencleary.com/2012/02/async-and-await.html) and my [`async` best practices article](https://msdn.microsoft.com/en-us/magazine/jj991977.aspx). – Stephen Cleary Oct 27 '16 at 13:25
  • I have read both posts before, and I understand what ConfigureAwait(false) does, I will rephrase my question: by invoking ConfigureAwait(false), you cause the task instance to resume on a different synchronization context than the original one- which is not necessarily "expected" by the code consuming this extension method- I wholeheartedly agree that the practice you suggest is a good one, I just can't conceive a decision process which justifies doing this on a utility level such as the said extension method. I hope I did not "overstep", I mean all of this with great respect. – Eyal Perry Oct 27 '16 at 14:01
  • @EyalPerry: Each `await` captures its own context. So the decision is made locally: Does the `WaitAsync` method need to resume on a particular context? No, so use `ConfigureAwait(false)`. Whether the *caller* needs to or not will be handled by the caller. – Stephen Cleary Oct 27 '16 at 14:03
  • I understand that, but still - a decision to "switch contexts" has been made as a result of the call to this method.. would it not be best to leave such a decision up to the original caller, by providing him a means to control this behavior.. just for transparency's sake? – Eyal Perry Oct 29 '16 at 08:41
  • @EyalPerry: Perhaps I'm not explaining it right. The `ConfigureAwait(false)` inside `DoWaitAsyncMethod` has absolutely no effect on the context of any method that calls it. When the caller does its `await`, it is still on the same context. – Stephen Cleary Oct 29 '16 at 10:37
  • What I understood is that eventually, there is some work, small as it may be, that is offloaded to a different synchronization context. This is opaque to the caller, and therefore rings an alarm bell in my head. Thank you kindly for your patience. – Eyal Perry Oct 29 '16 at 10:45
  • false doesn't mean a different context. It means you don't care which context. – Stephen Cleary Oct 29 '16 at 18:17
  • Then I guess I misunderstood what ConfigureAwait does. I will conduct further research. Thank you. – Eyal Perry Oct 29 '16 at 18:20