Convert async / wait to Task.ContinueWith

This question was triggered by the comments of this :

How to return non-linear async/await code in .NET 4.0 without Microsoft.Bcl.Async ?

In a related question, we perform the WebRequest operation, which we want to repeat for a limited number of times if it continues to fail. The async/await code might look like this:

 async Task<HttpWebResponse> GetResponseWithRetryAsync(string url, int retries) { if (retries < 0) throw new ArgumentOutOfRangeException(); var request = WebRequest.Create(url); while (true) { WebResponse task = null; try { task = request.GetResponseAsync(); return (HttpWebResponse)await task; } catch (Exception ex) { if (task.IsCanceled) throw; if (--retries == 0) throw; // rethrow last error // otherwise, log the error and retry Debug.Print("Retrying after error: " + ex.Message); } } } 

From the first thought, I would use TaskCompletionSource , something like this (untested):

 Task<HttpWebResponse> GetResponseWithRetryAsync(string url, int retries) { if (retries < 0) throw new ArgumentOutOfRangeException(); var request = WebRequest.Create(url); var tcs = new TaskCompletionSource<HttpWebResponse>(); Action<Task<WebResponse>> proceesToNextStep = null; Action doStep = () => request.GetResponseAsync().ContinueWith(proceedToNextStep); proceedToNextStep = (prevTask) => { if (prevTask.IsCanceled) tcs.SetCanceled(); else if (!prevTask.IsFaulted) tcs.SetResult((HttpWebResponse)prevTask.Result); else if (--retries == 0) tcs.SetException(prevTask.Exception); else doStep(); }; doStep(); return tcs.Task; } 

Question: how to do this without TaskCompletionSource ?

+6
source share
1 answer

I figured out how to do this without async/await or TaskCompletionSource , using nested tasks and Task.Unwrap .

First, for the @mikez comment address, here is GetResponseAsync for .NET 4.0:

 static public Task<WebResponse> GetResponseTapAsync(this WebRequest request) { return Task.Factory.FromAsync( (asyncCallback, state) => request.BeginGetResponse(asyncCallback, state), (asyncResult) => request.EndGetResponse(asyncResult), null); } 

Now, here is the GetResponseWithRetryAsync implementation:

 static Task<HttpWebResponse> GetResponseWithRetryAsync(string url, int retries) { if (retries < 0) throw new ArgumentOutOfRangeException(); var request = WebRequest.Create(url); Func<Task<WebResponse>, Task<HttpWebResponse>> proceedToNextStep = null; Func<Task<HttpWebResponse>> doStep = () => { return request.GetResponseTapAsync().ContinueWith(proceedToNextStep).Unwrap(); }; proceedToNextStep = (prevTask) => { if (prevTask.IsCanceled) throw new TaskCanceledException(); if (prevTask.IsFaulted && --retries > 0) return doStep(); // throw if failed or return the result return Task.FromResult((HttpWebResponse)prevTask.Result); }; return doStep(); } 

It was an interesting exercise. It works, but I think its path is more complicated than the async/await version.

+5
source

All Articles