微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

Fallback.AsyncFallbackEngine 异常

如何解决Fallback.AsyncFallbackEngine 异常

我在 .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.d.MoveNext() 在 ...\Services\UserMigrationService.cs:line 474
--- 从上一个抛出异常的位置开始的堆栈跟踪结束---

在 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.d.MoveNext()
--- 从上一个抛出异常的位置开始的堆栈跟踪结束---

在 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 可能是 nullExecuteAsync 接收一个委托,该委托可能与您的 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 函数。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。