Will this implementation really behave as expected when used with TPL?
Not.
- It will not mark the result of
Task<T> as canceled, so the behavior will not be as expected. - In the case of a timeout, the
WebException contained in the AggregateException indicated by Task.Exception will have the status WebExceptionStatus.RequestCanceled . Instead, it should be WebExceptionStatus.Timeout .
I would recommend using TaskCompletionSource<T> to implement this. This allows you to write code without creating your own APM style methods:
public static Task<WebResponse> GetResponseAsync(this WebRequest request, CancellationToken token) { if (request == null) throw new ArgumentNullException("request"); bool timeout = false; TaskCompletionSource<WebResponse> completionSource = new TaskCompletionSource<WebResponse>(); AsyncCallback completedCallback = result => { try { completionSource.TrySetResult(request.EndGetResponse(result)); } catch (WebException ex) { if (timeout) completionSource.TrySetException(new WebException("No response was received during the time-out period for a request.", WebExceptionStatus.Timeout)); else if (token.IsCancellationRequested) completionSource.TrySetCanceled(); else completionSource.TrySetException(ex); } catch (Exception ex) { completionSource.TrySetException(ex); } }; IAsyncResult asyncResult = request.BeginGetResponse(completedCallback, null); if (!asyncResult.IsCompleted) { if (request.Timeout != Timeout.Infinite) { WaitOrTimerCallback timedOutCallback = (object state, bool timedOut) => { if (timedOut) { timeout = true; request.Abort(); } }; ThreadPool.RegisterWaitForSingleObject(asyncResult.AsyncWaitHandle, timedOutCallback, null, request.Timeout, true); } if (token != CancellationToken.None) { WaitOrTimerCallback cancelledCallback = (object state, bool timedOut) => { if (token.IsCancellationRequested) request.Abort(); }; ThreadPool.RegisterWaitForSingleObject(token.WaitHandle, cancelledCallback, null, Timeout.Infinite, true); } } return completionSource.Task; }
The advantage is that your Task<T> result will work in full accordance with expectations (it will be marked as canceled or will be raised by the same exception with timeout information like the synchronous version, etc.). It also avoids the overhead of using Task.Factory.FromAsync , as you already handle most of the complex work yourself.
Addendum 280Z28
Here is a unit test showing the correct operation for the above method.
[TestClass] public class AsyncWebRequestTests { [TestMethod] public void TestAsyncWebRequest() { Uri uri = new Uri("http://google.com"); WebRequest request = HttpWebRequest.Create(uri); Task<WebResponse> response = request.GetResponseAsync(); response.Wait(); } [TestMethod] public void TestAsyncWebRequestTimeout() { Uri uri = new Uri("http://google.com"); WebRequest request = HttpWebRequest.Create(uri); request.Timeout = 0; Task<WebResponse> response = request.GetResponseAsync(); try { response.Wait(); Assert.Fail("Expected an exception"); } catch (AggregateException exception) { Assert.AreEqual(TaskStatus.Faulted, response.Status); ReadOnlyCollection<Exception> exceptions = exception.InnerExceptions; Assert.AreEqual(1, exceptions.Count); Assert.IsInstanceOfType(exceptions[0], typeof(WebException)); WebException webException = (WebException)exceptions[0]; Assert.AreEqual(WebExceptionStatus.Timeout, webException.Status); } } [TestMethod] public void TestAsyncWebRequestCancellation() { Uri uri = new Uri("http://google.com"); WebRequest request = HttpWebRequest.Create(uri); CancellationTokenSource cancellationTokenSource = new CancellationTokenSource(); Task<WebResponse> response = request.GetResponseAsync(cancellationTokenSource.Token); cancellationTokenSource.Cancel(); try { response.Wait(); Assert.Fail("Expected an exception"); } catch (AggregateException exception) { Assert.AreEqual(TaskStatus.Canceled, response.Status); ReadOnlyCollection<Exception> exceptions = exception.InnerExceptions; Assert.AreEqual(1, exceptions.Count); Assert.IsInstanceOfType(exceptions[0], typeof(OperationCanceledException)); } } [TestMethod] public void TestAsyncWebRequestError() { Uri uri = new Uri("http://google.com/fail"); WebRequest request = HttpWebRequest.Create(uri); Task<WebResponse> response = request.GetResponseAsync(); try { response.Wait(); Assert.Fail("Expected an exception"); } catch (AggregateException exception) { Assert.AreEqual(TaskStatus.Faulted, response.Status); ReadOnlyCollection<Exception> exceptions = exception.InnerExceptions; Assert.AreEqual(1, exceptions.Count); Assert.IsInstanceOfType(exceptions[0], typeof(WebException)); WebException webException = (WebException)exceptions[0]; Assert.AreEqual(HttpStatusCode.NotFound, ((HttpWebResponse)webException.Response).StatusCode); } } }
Reed copsey
source share