(The full set of ParallelExtensionsExtras Tour posts is available here.)
In our last ParallelExtensionsExtras tour post, we discussed implementing an extension ToObservable method for Task<TResult>. This is just one of a myriad of extra pieces of functionality that are useful with Tasks, and the TaskExtrasExtensions.cs file in ParallelExtensionsExtras includes several. In this post, we’ll cover a few of the more interesting ones.
WithTimeout
When dealing with operations that run asynchronously, it’s not uncommon to want to incorporate a timeout, such that other code that depends on the asynchronous operation isn’t forced to wait too long. We can tackle this use case with a simple WithTimeout method:
public static Task<TResult> WithTimeout<TResult>(
this Task<TResult> task, TimeSpan timeout)
{
var result = new TaskCompletionSource<TResult>(task.AsyncState);
var timer = new Timer(_ => result.TrySetCanceled(),
null, timeout, TimeSpan.FromMilliseconds(-1));
task.ContinueWith(t =>
{
timer.Dispose();
result.TrySetFromTask(t);
});
return result.Task;
}
This method works by creating a TaskCompletionSource to serve as the representation for the potentially timed-out task. A System.Threading.Timer instance is also created to fire once after the user specified period of time. When the timer does fire, it attempts to transition the TaskCompletionSource into a canceled state, which will only take effect if the TaskCompletionSource hasn’t already been completed. In addition, a continuation is used to dispose of the timer and to transition the TaskCompletionSource to a completed state when the original task finishes.
TrySetFromTask is another utility extension method defined in ParallelExtensionsExtras that serves to wrap up some frequently needed logic when using a TaskCompletionSource to serve as a proxy for another Task<TResult>:
public static bool TrySetFromTask<TResult>(
this TaskCompletionSource<TResult> resultSetter, Task<TResult> task)
{
switch (task.Status)
{
case TaskStatus.RanToCompletion:
return resultSetter.TrySetResult(task.Result);
case TaskStatus.Faulted:
return resultSetter.TrySetException(
task.Exception.InnerExceptions);
case TaskStatus.Canceled:
return resultSetter.TrySetCanceled();
default:
throw new InvalidOperationException(“The task was not completed.”);
}
}
TrySetFromTask is defined in TaskCompletionSourceExtensions.cs
WithAsyncCallback
Tasks implement IAsyncResult, which make them useful in custom implementations of the Asnchronous Programming Model (APM) pattern. However, when implementing the APM pattern, there are some very important rules that need to be followed. For example, the object state parameter passed to the BeginXx method must be returned from the resulting IAsyncResult’s AsyncState property, the AsyncCallback passed to BeginXx must be invoked when the asynchronous operation completes, and it must be invoked with the exact same IAsyncResult returned from the BeginXx method as its argument. We can implement a WithAsyncCallback method to accomplish all of this:
public static Task WithAsyncCallback(
this Task task, AsyncCallback callback, object state)
{
var tcs = new TaskCompletionSource<object>(state);
task.ContinueWith(_ =>
{
tcs.TrySetFromTask(task);
if (callback != null) callback(tcs.Task);
});
return tcs.Task;
}
As with WithTimeout, a TaskCompletionSource instance is used, here to represent the IAsyncResult that should be both returned from BeginXx and passed to the AsyncCallback. When the source task completes, using ContinueWith we transfer the task’s results to the TaskCompletionSource and we then invoke the AsyncCallback.
0 comments