The idea here is simple, but the implementation has some interesting nuances. This is the signature of the extension method I would like to implement in .NET 4.
public static Task<WebResponse> GetResponseAsync(this WebRequest request, CancellationToken token);
Here is my initial implementation. From what I've read, the web request might need to be cancelled due to a timeout. In addition to the support described on that page, I want to properly call request.Abort()
if cancellation is requested via the CancellationToken
.
public static Task<WebResponse> GetResponseAsync(this WebRequest request, CancellationToken token)
{
if (request == null)
throw new ArgumentNullException("request");
return Task.Factory.FromAsync<WebRequest, CancellationToken, WebResponse>(BeginGetResponse, request.EndGetResponse, request, token, null);
}
private static IAsyncResult BeginGetResponse(WebRequest request, CancellationToken token, AsyncCallback callback, object state)
{
IAsyncResult asyncResult = request.BeginGetResponse(callback, state);
if (!asyncResult.IsCompleted)
{
if (request.Timeout != Timeout.Infinite)
ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, WebRequestTimeoutCallback, request, request.Timeout, true);
if (token != CancellationToken.None)
ThreadPool.RegisterWaitForSingleObject(token.WaitHandle, WebRequestCancelledCallback, Tuple.Create(request, token), Timeout.Infinite, true);
}
return asyncResult;
}
private static void WebRequestTimeoutCallback(object state, bool timedOut)
{
if (timedOut)
{
WebRequest request = state as WebRequest;
if (request != null)
request.Abort();
}
}
private static void WebRequestCancelledCallback(object state, bool timedOut)
{
Tuple<WebRequest, CancellationToken> data = state as Tuple<WebRequest, CancellationToken>;
if (data != null && data.Item2.IsCancellationRequested)
{
data.Item1.Abort();
}
}
My question is simple yet challenging. Will this implementation actually behave as expected when used with the TPL?
No.
Task<T>
result as cancelled, so the behavior will not be exactly as expected.WebException
contained in theAggregateException
reported byTask.Exception
will have the statusWebExceptionStatus.RequestCanceled
. It should instead beWebExceptionStatus.Timeout
.I would actually recommend using
TaskCompletionSource<T>
to implement this. This allows you to write the code without making your own APM style methods:The advantage here is that your
Task<T>
result will work fully as expected (will be flagged as canceled, or raise the same exception with timeout info as synchronous version, etc). This also avoids the overhead of usingTask.Factory.FromAsync
, since you're already handling most of the difficult work involved there yourself.Addendum by 280Z28
Here is a unit test showing proper operation for the method above.