As others have already mentioned, there is no clean way of achieving what you're asking for. The notion of cancellation was absent from the Asynchronous Programming Model; thus, it couldn't be retrofitted through the FromAsync
converters.
However, you can introduce cancellation for the Task
that wraps the asynchronous operation. This will not cancel the underlying operation itself – your NetworkStream
would still continue reading all the requested bytes from the socket – but it will permit your application to react as if the operation was cancelled, immediately throwing an OperationCanceledException
from your await
(and executing any registered task continuations). The result of the underlying operation, once completed, will be ignored.
This is a helper extension method:
public static class TaskExtensions
{
public async static Task<TResult> HandleCancellation<TResult>(
this Task<TResult> asyncTask,
CancellationToken cancellationToken)
{
// Create another task that completes as soon as cancellation is requested.
// http://stackoverflow.com/a/18672893/1149773
var tcs = new TaskCompletionSource<TResult>();
cancellationToken.Register(() =>
tcs.TrySetCanceled(), useSynchronizationContext: false);
var cancellationTask = tcs.Task;
// Create a task that completes when either the async operation completes,
// or cancellation is requested.
var readyTask = await Task.WhenAny(asyncTask, cancellationTask);
// In case of cancellation, register a continuation to observe any unhandled
// exceptions from the asynchronous operation (once it completes).
// In .NET 4.0, unobserved task exceptions would terminate the process.
if (readyTask == cancellationTask)
asyncTask.ContinueWith(_ => asyncTask.Exception,
TaskContinuationOptions.OnlyOnFaulted |
TaskContinuationOptions.ExecuteSynchronously);
return await readyTask;
}
}
And this is an example that uses the extension method to treat an operation as cancelled after 300ms:
CancellationTokenSource cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromMilliseconds(300));
try
{
int bytesRead =
await Task<int>.Factory.FromAsync(this.stream.BeginRead, this.stream.EndRead, buffer, 0, buffer.Length, null)
.HandleCancellation(cts.Token);
}
catch (OperationCanceledException)
{
// Operation took longer than 300ms, and was treated as cancelled.
}