这个问题是由对 this one 的评论触发的:
如何在没有 Microsoft.Bcl.Async
的情况下将非线性 async/await
代码反向移植到 .NET 4.0?
在链接的问题中,我们有一个 WebRequest
操作,如果它一直失败,我们希望重试有限的次数。 Async/await
代码可能如下所示:
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);
}
}
}
从最初的想法来看,我会使用 TaskCompletionSource
,就像这样(未经测试):
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;
}
问题是,如何在没有 TaskCompletionSource
的情况下执行此操作?
最佳答案
我已经想出了如何在没有 async/await
或 TaskCompletionSource
的情况下,使用嵌套任务和 Task.Unwrap
来做到这一点。
首先,为了解决@mikez 的评论,这里是 .NET 4.0 的 GetResponseAsync
实现:
static public Task<WebResponse> GetResponseTapAsync(this WebRequest request)
{
return Task.Factory.FromAsync(
(asyncCallback, state) =>
request.BeginGetResponse(asyncCallback, state),
(asyncResult) =>
request.EndGetResponse(asyncResult), null);
}
现在,这是 GetResponseWithRetryAsync
实现:
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();
}
这是一个有趣的练习。它有效,但我认为它比 async/await
版本更难遵循。
关于c# - 将异步/等待转换为 Task.ContinueWith,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21345673/