使用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
相关产品推荐
相关产品推荐

