Question

I'm interested in efficiently using system resources in conjunction with the Task Parallel Library and continuations.

Consider the following scenario which uses the GetResponseAsync() extension method defined in another recent question.

WebRequest request = HttpWebRequest.Create(uri);
Task<WebResponse> responseTask = request.GetResponseAsync(cancellationToken);
Func<Task<WebResponse>, WebResponse> continuation =
    task =>
    {
        WebRequest followup = HttpWebRequest.Create(uri2);
        return followup.GetResponseAsync(cancellationToken).Result;
    };
Task<WebResponse> finalResultTask = responseTask.ContinueWith(continuation, cancellationToken);

There are multiple problems with this configuration, and I'm wondering how this can best be handled. The major items I've identified so far are:

  1. The execution of the core of responseTask efficiently uses resources by not blocking a user thread during the asynchronous execution. However, since the continuation is defined as a Func lambda, the thread executing the continuation will block on the return line until the execution of the follow-up request is complete. A better situation would provide continuation-like behavior while not blocking a user thread.

  2. The behavior of responseTask and finalResultTask differ in regards to cancellation. If operation is cancelled while responseTask is executing, responseTask will enter the state TaskStatus.Canceled. However, if the operation is canceled while finalResultTask is executing, the attempt to access the Result property will result in an exception, causing the task to enter the TaskStatus.Failed state.

    • The behavior may also differ in regards to failure. If an AggregateException is thrown when attempting to access the Result property when returning from the continuation, then finalResultTask may have a doubly-wrapped true inner exception (I'm not sure if a special case occurs here), where the InnerException property of responseTask.Exception would provide direct access to the actual exception should it fail during the first task.
Was it helpful?

Solution

I suspect that both your problems would be resolved if you use the Unwrap extension method. Your goal is to return a new task as a result of your continuation function; however, since the continuation itself is executed as a task, this would lead to an extra level of nesting (task returning task). Thus, you need to eliminate this nesting using Unwrap, which will give you a proxy task that is bound to the result of the inner task.

WebRequest request = HttpWebRequest.Create(uri);
Task<WebResponse> responseTask = request.GetResponseAsync(cancellationToken);
Func<Task<WebResponse>, Task<WebResponse> continuation =
    task =>
    {
        WebRequest followup = HttpWebRequest.Create(uri2);
        return followup.GetResponseAsync(cancellationToken);
    };

Task<Task<WebResponse>> finalResultTask = responseTask.ContinueWith(continuation);
Task<WebResponse> proxyTask = finalResultTask.Unwrap();

Quick optimization: Since your continuation function does little other than spawn off a new task through GetResponseAsync, you can reduce its execution overhead by specifying it as ExecuteSynchronously:

Task<Task<WebResponse>> finalResultTask = responseTask.ContinueWith(continuation, 
    TaskContinuationOptions.ExecuteSynchronously);

Edit: Per Servy's suggestion, you should pass your CancellationToken to your continuation function too:

Task<Task<WebResponse>> finalResultTask = responseTask.ContinueWith(continuation, 
    cancellationToken,
    TaskContinuationOptions.ExecuteSynchronously,
    TaskScheduler.Current);
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top