c# - 将异步/等待转换为 Task.ContinueWith

标签 c# .net asynchronous task-parallel-library async-await

这个问题是由对 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/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 版本更难遵循。

关于c# - 将异步/等待转换为 Task.ContinueWith,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/21345673/

相关文章:

c# - WCF 使用凭据和 IIS 以编程方式启用 TLS

javascript - C#和javascript之间的加密结果差异

c# - 使用 Linq 重新排列表数据

Android:BLE如何读取多个特征?

multithreading - 需要在 Tomcat 中解释 Workmanager 的示例代码

c# - 气泡图\注入(inject)图片而不是气泡

c# - 打开MediaPlayer-线程中未处理事件

.net - 在 VS 2010 中调试时修改代码

.net - 每次查看表单时,WinForms 控件都会错位

C# 此时无法启动异步操作。