10

I am writing a Stream class and am blocked in the ReadAsync method. Please take a look at the code, I think it can explain the situation better that I can do it with my English.

public override Task<int> ReadAsync(byte[] buffer, int offset, int count, System.Threading.CancellationToken cancellationToken)
{
    if (!cancellationToken.IsCancellationRequested)
    {
        return _connection.ReceiveAsync(new ArraySegment<byte>(buffer, offset, count));
    }
    return // <---------------    what here?
}

Using ILSpy I can see that other Stream classes return a cancelled task as follow:

return new Task<TResult>(true, default(TResult), TaskCreationOptions.None, cancellationToken); 

However that Task's constructor is internal and I cannot invoke it.

Google didn't help me at all.

lontivero
  • 5,235
  • 5
  • 25
  • 42

2 Answers2

16

The next version of .Net (v4.5.3) is adding a way to create a cancelled task leveraging that internal constructor. There are both a generic and non-generic versions:

var token = new CancellationToken(true);
Task task = Task.FromCanceled(token);
Task<int> genericTask = Task.FromCanceled<int>(token);

Keep in mind that the CancellationToken being used must be canceled before calling FromCanceled

Pang
  • 9,564
  • 146
  • 81
  • 122
i3arnon
  • 113,022
  • 33
  • 324
  • 344
15

The most direct way I know to create a canceled task is to use a TaskCompletionSource:

var tcs = new TaskCompletionSource<int>();
tcs.TrySetCanceled();
return tcs.Task;

If you haven't used it before, TaskCompletionSource provides a "promise-style" task, which basically allows you to say, "Here, take this Task now, and I'll provide the result (or report an error/cancellation) whenever I'm ready." It's useful when you want to schedule/coordinate work yourself, as opposed to simply relying on a TaskScheduler.

Alternatively, if you rewrite your method using async/await, you can force the cancellation exception to automatically propagate to the result Task:

public async override Task<int> ReadAsync(
    byte[] buffer,
    int offset,
    int count,
    CancellationToken cancellationToken)
{
    cancellationToken.ThrowIfCancellationRequested();

    return await _connection.ReceiveAsync(
        new ArraySegment<byte>(
            buffer,
            offset,
            count));
}
Mike Strobel
  • 25,075
  • 57
  • 69
  • I don't like the idea of calling `ThrowIfCancellationRequested` because of the necessary runtime expense of throwing an exception and unwinding the stack - when I could just do `if( token.IsCancelationRequested ) return Task.FromCanceled( token )` but how do I do that in an `async` method? – Dai Aug 18 '17 at 00:01
  • @Dai You can't; once you mark the method as `async Task`, you must return a `T`. I wouldn't worry about the overhead of throwing the exception. Optimize for the common case. Cancellation should be uncommon. This is the idiomatic way to propagate cancellation within an `async` method. – Mike Strobel Aug 19 '17 at 01:30