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

使用AsyncInterceptor+Polly实现AOP重试:异步方法无等待间隔问题排查

问题根源分析

你的异步重试间隔不生效的核心问题在于Castle IInvocation.Proceed() 方法在异步场景下只能被调用一次,而你的Polly重试逻辑每次都重复调用invocation.Proceed(),导致后续重试没有真正重新执行仓储的异步方法,而是复用了第一次执行时返回的已失败Task。


排查思路

1. 验证仓储异步方法是否被重复执行

在你的GetByIdAsync方法中添加日志输出,确认重试时是否多次进入该方法:

public async Task<Entity> GetByIdAsync(int id) {
    Console.WriteLine("Entering GetByIdAsync"); // 添加日志排查
    if (_exceptionsCoutner <= 2) {
        _exceptionsCoutner++;
        throw new DatabaseException();
    }
    return await Task.FromResult(new Entity { Id = id });
}

如果日志只输出一次,说明后续重试没有重新执行目标方法,问题出在拦截器的Proceed()调用逻辑上。

2. 检查Polly重试计数是否正常递增

修改Polly的日志委托,添加重试次数参数,确认每次重试的计数是否正确:

.WaitAndRetryAsync(RetryIntervals, (exception, timeSpan, retryCount) => { 
    Console.WriteLine($"Exception {timeSpan}, Retry Count: {retryCount}"); 
})

如果重试计数是递增的(0→1→2),说明Polly本身的重试逻辑正常,只是目标方法没有被重新执行;如果计数始终为0,说明Polly没有正确识别重试场景(这种情况可能性极低)。

3. 确认Castle AsyncInterceptor的Proceed()行为

根据Castle Windsor官方文档,IInvocation.Proceed()在异步调用中仅应被调用一次——它触发目标方法的执行,并将结果赋值给invocation.ReturnValue。多次调用Proceed()会导致未定义行为,通常不会重新执行目标方法,而是直接返回之前的Task结果。


解决方案

替换异步拦截中依赖invocation.Proceed()的逻辑,改用反射直接调用仓储的目标方法,确保每次重试都重新执行方法:

private async Task<TResult> InternalInterceptAsync<TResult>(IInvocation invocation) {
    return await Policy
        .Handle<DatabaseException>()
        .WaitAndRetryAsync(RetryIntervals, (exception, timeSpan) => { 
            Console.WriteLine($"Exception {timeSpan}"); 
        })
        .ExecuteAsync(async () => {
            // 通过反射调用目标方法,每次重试都会重新执行
            var task = (Task<TResult>)invocation.Method.Invoke(
                invocation.InvocationTarget, 
                invocation.Arguments
            );
            return await task;
        });
}

同样,建议同步拦截也改用反射调用,保持逻辑一致:

private object InternalInterceptSync(IInvocation invocation) {
    return Policy
        .Handle<DatabaseException>()
        .WaitAndRetry(RetryIntervals, (exception, timeSpan) => { 
            Console.WriteLine($"Exception {timeSpan}"); 
        })
        .Execute(() => {
            return invocation.Method.Invoke(
                invocation.InvocationTarget, 
                invocation.Arguments
            );
        });
}

这样修改后,每次重试都会真正重新执行仓储的方法,计数器会正常递增,Polly的重试间隔也会按预期生效。

内容的提问来源于stack exchange,提问作者Александр Сысоев

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.12 04:55:37