views:

193

answers:

1

I have a task to perform an HttpWebRequest using

 Task<WebResponse>.Factory.FromAsync(req.BeginGetRespone, req.EndGetResponse)

which can obviously fail with a WebException. To the caller I want to return a Task<HttpResult> where HttpResult is a helper type to encapsulate the response (or not). In this case a 4xx or 5xx response is not an exception.

Therefore I've attached two continuations to the request task. One with TaskContinuationOptions OnlyOnRanToCompletion and the other with OnlyOnOnFaulted. And then wrapped the whole thing in a Task<HttpResult> to pick up the one result whichever continuation completes.

Each of the three child tasks (request plus two continuations) is created with the AttachedToParent option.

But when the caller waits on the returned outer task, an AggregateException is thrown is the request failed.

I want to, in the on faulted continuation, observe the WebException so the client code can just look at the result. Adding a Wait in the on fault continuation throws, but a try-catch around this doesn't help. Nor does looking at the Exception property (as section "Observing Exceptions By Using the Task.Exception Property" hints here).

I could install a UnobservedTaskException event handler to filter, but as the event offers no direct link to the faulted task this will likely interact outside this part of the application and is a case of a sledgehammer to crack a nut.

Given an instance of a faulted Task<T> is there any means of flagging it as "fault handled"?

Simplified code:

public static Task<HttpResult> Start(Uri url) {
    var webReq = BuildHttpWebRequest(url);
    var result = new HttpResult();
    var taskOuter = Task<HttpResult>.Factory.StartNew(() => {
        var tRequest = Task<WebResponse>.Factory.FromAsync(
                            webReq.BeginGetResponse,
                            webReq.EndGetResponse,
                            null, TaskCreationOptions.AttachedToParent);
        var tError = tRequest.ContinueWith<HttpResult>(
                            t => HandleWebRequestError(t, result),
                            TaskContinuationOptions.AttachedToParent
                            |TaskContinuationOptions.OnlyOnFaulted);
        var tSuccess = tRequest.ContinueWith<HttpResult>(
                            t => HandleWebRequestSuccess(t, result),
                            TaskContinuationOptions.AttachedToParent
                            |TaskContinuationOptions.OnlyOnRanToCompletion);
        return result;
    });

    return taskOuter;
}

with:

private static HttpDownloaderResult HandleWebRequestError(
                                        Task<WebResponse> respTask, 
                                        HttpResult result) {
    Debug.Assert(respTask.Status == TaskStatus.Faulted);
    Debug.Assert(respTask.Exception.InnerException is WebException);
    // Try and observe the fault: Doesn't help.
    try {
        respTask.Wait();
    } catch (AggregateException e) {
        Log("HandleWebRequestError: waiting on antecedent task threw inner: "
             + e.InnerException.Message);
    }
    // ... populate result with details of the failure for the client ...
    return result;
}

(HandleWebRequestSuccess will eventually spin off further tasks to get the content of the response...)

The client should be able to wait on the task and then look at its result, without it throwing due to a fault that is expected and already handled.

A: 

In the end I took the simplest route I could think of: hide the exception. This is possible because WebException has a property Response which gives access to the HttpWebResponse I want:

var requestTask = Task<WebResponse>.Factory.FromAsync(
                        webReq.BeginGetResponse,
                        ia => {
                          try {
                            return webReq.EndGetResponse(ia);
                          } catch (WebException exn) {
                            requestState.Log(...);
                            return exn.Response;
                          }
                        });

And then handle errors, redirects and success responses in the continuation task.

Richard