问题描述
我在 .NET Framework 4.8 (WCF) 中有一个应用程序,它进行 http 调用,也使用 Polly 进行重试和回退管理,但有时会引发 System.NullReferenceException
,但我不知道在哪里问题。这是代码:
private static async Task<bool> CallApi<TRequest>(TRequest request,string requestUri,Func<string,StringContent,Task<HttpResponseMessage>> func)
{
var jsonRequest = JsonConvert.SerializeObject(request);
var fallBackPolicy = Policy<HttpResponseMessage>.Handle<Exception>()
.FallbackAsync(new HttpResponseMessage(HttpStatusCode.SeeOther)
{
Content = new StringContent($"Exception has occurred in migration call. RequestUri: {requestUri}")
},result =>
{
LogEventService.Logger.Error(result.Exception,"An unhandled exception occurred while retrying calling");
return Task.CompletedTask;
});
var waitAndRetryPolicy = Policy.HandleResult<HttpResponseMessage>(res => res.StatusCode == HttpStatusCode.InternalServerError).
WaitAndRetryAsync(2,retryAttempts => TimeSpan.FromMilliseconds(500));
var response = await fallBackPolicy
.WrapAsync(waitAndRetryPolicy)
.ExecuteAsync(async () =>
{
using (var content = new StringContent(jsonRequest,Encoding.UTF8,"application/json"))
{
content.Headers.Add("X-Correlation-ID",HttpContext.Current.Session[RequestId].ToString());
return await func(requestUri,content);
}
});
if(response.IsSuccessstatusCode)
return true;
await LogMessage(LogLevel.Error,response,requestUri);
return false;
}
这是堆栈跟踪
System.NullReferenceException: 未将对象引用设置为实例
一个对象。
在
UserMigrationService.c__displayClass22_0'1.
--- 从上一个抛出异常的位置开始的堆栈跟踪结束---
在
System.Runtime.ExceptionServices.ExceptiondispatchInfo.Throw()
在
Polly.Retry.AsyncRetryEngine.d__0'1.MoveNext()
--- 从上一个抛出异常的位置开始的堆栈跟踪结束---
在 System.Runtime.ExceptionServices.ExceptiondispatchInfo.Throw()
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
在 Polly.AsyncPolicy'1.d__13.MoveNext()
--- 从上一个抛出异常的位置开始的堆栈跟踪结束---
在 System.Runtime.ExceptionServices.ExceptiondispatchInfo.Throw()
在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
在
Polly.Wrap.AsyncPolicyWrapEngine.c__displayClass0_0'1.
--- 从上一个抛出异常的位置开始的堆栈跟踪结束---
在 System.Runtime.ExceptionServices.ExceptiondispatchInfo.Throw() > 在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
在 Polly.Fallback.AsyncFallbackEngine.d__0'1.MoveNext()
你能帮我找出错误的部分在哪里吗?我怎样才能更好地理解为什么会抛出这个异常?
谢谢大家
解决方法
事实证明 NRE 是在这一行抛出的:
ontent.Headers.Add("X-Correlation-ID",HttpContext.Current.Session[RequestId].ToString());
这表明 HttpContext.Current
可能是 null
。 ExecuteAsync
接收一个委托,该委托可能与您的 CallApi
方法的其余代码不在同一线程上运行。
这就是 HttpContext
不会流入委托的原因。
修复非常简单:您必须在 RequestId
中捕获 CallApi
,而不是在 ExecuteAsync
委托中:
var correlationId = HttpContext.Current.Session[RequestId].ToString();
var response = await fallBackPolicy
.WrapAsync(waitAndRetryPolicy)
.ExecuteAsync(async () =>
{
using (var content = new StringContent(jsonRequest,Encoding.UTF8,"application/json"))
{
content.Headers.Add("X-Correlation-ID",correlationId);
return await func(requestUri,content);
}
});
我还建议使用 Policy.Wrap
(Reference) 而不是在其中一项策略上调用 WrapAsync
方法。以下两行是等价的:
fallBackPolicy.WrapAsync(waitAndRetryPolicy)
Policy.WrapAsync(fallbackPolicy,waitAndRetryPolicy)
所以,你的代码可以这样重写:
var correlationId = HttpContext.Current.Session[RequestId].ToString();
var strategy = Policy.WrapAsync(fallbackPolicy,waitAndRetryPolicy);
var response = await strategy
.ExecuteAsync(async (ct) =>
{
using (var content = new StringContent(jsonRequest,MediaTypeNames.Application.Json))
{
content.Headers.Add("X-Correlation-ID",correlationId);
//TODO: pass the cancellationToken to the func
return await func(requestUri,content);
}
},CancellationToken.None);
我使用了 ExecuteAsync
的其他重载,其中您收到了 CancellationToken
。每当您考虑使用 TimeoutPolicy 时,这都非常有用。在这种情况下,您应该将该令牌传递给 func
函数。