将async/await转换为Task.ContinueWith

7
这个问题是由此问题的评论引起的:
如果没有使用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的情况下完成此操作?

2
另外,在 .NET 4 中并不存在 GetResponseAsync 方法,您需要使用较旧的 BeginGetResponse/EndGetResponse 方法。 - Mike Zboray
@mikez,没错,我在这里解决了它(https://dev59.com/1Xvaa4cB1Zd3GeqPG7lU#21346870)。 - noseratio - open to work
1个回答

6

我已经找到了一种无需使用async/awaitTaskCompletionSource,而是使用嵌套任务和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版本更难理解。


1
这个.NET 4的答案很棒。这个问题源于使用.NET 4.5+实现的WebRequest,但它不可用,因为最终会成为一个仅限于.NET 4的PCL。 - Daniel Park
3
FYI,Task.FromResult()是一个.NET 4.5的方法。 - Daniel Park
@DanielPark,关于Task.FromResult的观点很好。这将带回TaskCompletionSource以模拟Task.FromResult:https://dev59.com/CmYq5IYBdhLWcg3wujDF#14244239 - noseratio - open to work

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接