You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用Polly的ExecuteAsync时匿名Lambda函数return后未返回调用方

解决Polly ExecuteAsync中async/await导致返回值丢失的问题

核心问题分析

你的代码存在两个关键错误,直接导致返回值丢失、线程异常退出:

1. Polly策略与ExecuteAsync泛型不匹配

你定义的retryPolicy是非泛型的AsyncRetryPolicy,但调用ExecuteAsync时传入的是返回Task<T>的async lambda。此时必须使用泛型版本的ExecuteAsync<T>,否则Polly无法正确捕获lambda的返回值,导致lambda内部的return objectResponse_.Object无法传递到外层方法,最终方法直接走到末尾的默认返回,甚至因为异步状态未正确处理导致线程直接退出。

2. 方法末尾多余的默认返回

在await retryPolicy.ExecuteAsync(...)之后,你额外写了return default(T);。如果Polly正确执行并返回结果,这个语句永远不会执行,但因为前面的泛型不匹配问题,ExecuteAsync的返回值被忽略,方法直接走到这里返回默认值,同时异步上下文出现异常导致线程退出。

修复后的代码

public async Task<T> SendRequestAsync<T>(HttpRequestMessage request, CancellationToken cancellationToken, bool someBool = true)
{
    var retryPolicy = Policy.Handle<Exception>().Or<HttpRequestException>().RetryAsync(3, (exception, retryCount, context) =>
    {
        _logger.Log($"API call failed. Doing retry attempt {retryCount}");
    });

    // 使用泛型版本ExecuteAsync<T>,正确捕获lambda返回值
    return await retryPolicy.ExecuteAsync<T>(async () =>
    {
        // 重试时必须克隆请求,原request发送后会被自动Dispose
        using var clonedRequest = CloneRequest(request);
        using var response = await _client.SendAsync(clonedRequest, HttpCompletionOption.ResponseHeadersRead, cancellationToken).ConfigureAwait(false);
        
        var headers = response.Headers.ToDictionary(h => h.Key, h => h.Value);
        if (response.Content?.Headers != null)
        {
            foreach (var item in response.Content.Headers)
            {
                headers[item.Key] = item.Value;
            }
        }

        var status = (int)response.StatusCode;
        switch (status)
        {
            case 200:
            case 201:
            case 204:
                {
                    if (someBool)
                    {
                        var objectResponse_ = await ReadObjectResponseAsync<T>(response, headers, cancellationToken).ConfigureAwait(false);
                        if (objectResponse_.Object == null)
                        {
                            throw new MyException("Response was null which was not expected.", status, objectResponse_.Text, headers, null);
                        }
                        return objectResponse_.Object;
                    }
                    else
                    {
                        var objectResponse_ = await ReadObjectResponseAsync<T>(response, headers, cancellationToken).ConfigureAwait(false);
                        return objectResponse_.Object ?? default(T);
                    }
                }
            case 404:
                {
                    if (someBool)
                    {
                        var objectResponse_ = await ReadObjectResponseAsync<T>(response, headers, cancellationToken).ConfigureAwait(false);
                        return objectResponse_.Object;
                    }

                    var responseText = response.Content == null ? string.Empty : await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                    throw new MyException("Not Found", status, responseText, headers, null);
                }
            case 500:
                {
                    if (someBool)
                    {
                        var objectResponse_ = await ReadObjectResponseAsync<T>(response, headers, cancellationToken).ConfigureAwait(false);
                        return objectResponse_.Object;
                    }

                    var objectResponse = await ReadObjectResponseAsync<MyResponse>(response, headers, cancellationToken).ConfigureAwait(false);
                    if (objectResponse.Object == null)
                    {
                        throw new MyException("Response was null which was not expected.", status, objectResponse.Text, headers, null);
                    }

                    throw new MyException<MyResponse>("Internal Server Error", status, objectResponse.Object.Message, headers, objectResponse.Object, null);
                }
            default:
                {
                    var responseData = response.Content == null ? null : await response.Content.ReadAsStringAsync().ConfigureAwait(false);
                    throw new MyException($"The HTTP status code of the response was not expected ({status}).", status, responseData, headers, null);
                }
        }
    }).ConfigureAwait(false);

    // 移除多余的默认返回,ExecuteAsync<T>已返回结果
}

// 辅助方法:克隆HttpRequestMessage用于重试
private HttpRequestMessage CloneRequest(HttpRequestMessage request)
{
    var clone = new HttpRequestMessage(request.Method, request.RequestUri);
    clone.Content = request.Content;
    clone.Version = request.Version;
    foreach (var header in request.Headers)
    {
        clone.Headers.TryAddWithoutValidation(header.Key, header.Value);
    }
    return clone;
}

额外注意事项

  • HttpRequestMessage不可复用:同一个HttpRequestMessage被SendAsync调用后会被自动释放,重试时必须创建新的请求实例,否则会抛出ObjectDisposedException,代码中添加了CloneRequest辅助方法解决此问题。
  • 统一使用ConfigureAwait(false):所有await调用后添加.ConfigureAwait(false),避免不必要的上下文切换,保持异步代码一致性。
  • Polly泛型策略匹配:当需要返回值时,必须使用ExecuteAsync<T>匹配返回Task<T>的lambda,确保返回值能正确传递到外层方法。

内容的提问来源于stack exchange,提问作者AIK DO

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.16 15:30:58