This project has moved. For the latest updates, please go here.

Force task to be cancellable

Jul 25, 2014 at 9:46 AM
While it's obviously impossible to truly kill a task (except by aborting the thread), it is often sufficient to let the await complete since TaskCanceledException is usually followed by cleanup code that kills the task in some way.

Network communication is an obvious example. Individual async calls cannot be cancelled. The supplied cancellation token is ignored. But if await somehow terminates, cleanup code will close the connection, which actually terminates all hanging tasks.

So what I need is some way to have await complete even though the task hangs, most likely by creating TaskCompletionSource that combines arbitrary Task with CancellationToken.

I haven't found any such functionality in AsyncEx, but perhaps I have overlooked something. I have written helper methods like this:
public static Task ForceCancellable(this Task inner, CancellationToken cancellation)
{
    var source = new TaskCompletionSource();
    var registration = cancellation.Register(() => source.TrySetCanceled());
    inner.ContinueWith(t =>
    {
        registration.Dispose();
        if (t.IsCanceled)
            source.TrySetCanceled();
        else if (t.IsFaulted)
            source.TrySetException(t.Exception);
        else
            source.TrySetResult();
    });
    return source.Task;
}

public static Task<T> ForceCancellable<T>(this Task<T> inner, CancellationToken cancellation)
{
    var source = new TaskCompletionSource<T>();
    var registration = cancellation.Register(() => source.TrySetCanceled());
    inner.ContinueWith(t =>
    {
        registration.Dispose();
        if (t.IsCanceled)
            source.TrySetCanceled();
        else if (t.IsFaulted)
            source.TrySetException(t.Exception);
        else
            source.TrySetResult(t.Result);
    });
    return source.Task;
}
What do you think? Would this fit in AsyncEx?
Coordinator
Jul 26, 2014 at 1:26 AM
Thanks for sharing! I did consider adding similar functionality, but I decided not to.

In my experience, it's too confusing to have a CT cancel the await, rather than the task. It's not clear enough that the task is still actually running even when the await got canceled. You and I understand it, sure, but this is something I find confuses most devs.

Instead, AsyncEx has a CT.AsTask() extension method, which allows constructs like:
var completedTask = await Task.WhenAny(innerTask, token.AsTask());
I think WhenAny more clearly describes what's actually going on.

However, there's nothing wrong with using something like ForceCancellable in your own project, as long as your team understands the semantics. On a side note, you may want to use TCS.TryCompleteFromCompletedTask (part of AsyncEx) because the logic is easy to get wrong; in your posted code, any exception from the inner task would be wrapped in an AggregateException when it is propagated to the returned task.

-Steve