异步拦截器抛出异常未被ExceptionMiddleware捕获问题排查
我有一个继承自MethodInterception类的切面类,在该类中抛出异常时,ExceptionMiddleware的HandleExceptionAsync方法未触发。我使用Castle.DynamicProxy实现方法拦截,由于它在异步方法中存在运行问题,因此通过Castle.Core.AsyncInterceptor扩展解决,但现在异步拦截器抛出的异常无法被异常中间件处理。若不将OnBefore方法定义为async,异常中间件可正常工作,但我需要在OnBefore中执行await操作,因此必须使用async。
ExceptionMiddleware代码
public class ExceptionMiddleware { private RequestDelegate _next; public ExceptionMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext httpContext) { try { await _next(httpContext); } catch (Exception e) { await HandleExceptionAsync(httpContext, e); } } private Task HandleExceptionAsync(HttpContext httpContext, Exception e) { httpContext.Response.ContentType = "application/json"; httpContext.Response.StatusCode = (int)HttpStatusCode.InternalServerError; string message = "Internal Server Error"; if (e.GetType() == typeof(UnauthorizedAccessException)) { message = e.Message; httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized; } return httpContext.Response.WriteAsync(new ErrorDetails { StatusCode = httpContext.Response.StatusCode, Message = message }.ToString()); } }
SecuredOperation代码
public class SecuredOperation : MethodInterception { private string[] _roles; private IHttpContextAccessor _httpcontextAccessor; public SecuredOperation() { _httpcontextAccessor = ServiceTool.ServiceProvider.GetService<IHttpContextAccessor>(); } public SecuredOperation(string roles) { _roles = roles.Split(','); _httpcontextAccessor = ServiceTool.ServiceProvider.GetService<IHttpContextAccessor>(); } protected override async void OnBefore(IInvocation invocation) { if (_httpcontextAccessor.HttpContext.User.Identity.IsAuthenticated) { if (_roles != null) { // some await proccess var result = await _fga.check(); if(result != null) return; throw new MemberAccessException(AuthMessages.AuthorizationForbidden); } return; } throw new UnauthorizedAccessException(AuthMessages.AuthorizationDenied); } }
MethodInterception代码
public abstract class MethodInterception : MethodInterceptionBaseAttribute { protected virtual void OnBefore(IInvocation invocation) { } protected virtual void OnAfter(IInvocation invocation) { } protected virtual void OnException(IInvocation invocation, System.Exception e) { } protected virtual void OnSuccess(IInvocation invocation) { } public override void InterceptSynchronous(IInvocation invocation) { var isSuccess = true; OnBefore(invocation); try { invocation.Proceed(); } catch (Exception e) { isSuccess = false; OnException(invocation, e); throw; } finally { if (isSuccess) { OnSuccess(invocation); } } OnAfter(invocation); } public override void InterceptAsynchronous(IInvocation invocation) { invocation.ReturnValue = InternalInterceptAsynchronous(invocation); } protected async Task InternalInterceptAsynchronous(IInvocation invocation) { var isSuccess = true; OnBefore(invocation); try { invocation.Proceed(); var task = (Task)invocation.ReturnValue; await task; } catch (Exception e) { isSuccess = false; OnException(invocation, e); throw; } finally { if (isSuccess) { OnSuccess(invocation); } } OnAfter(invocation); } public override void InterceptAsynchronous<TResult>(IInvocation invocation) { invocation.ReturnValue = InternalInterceptAsynchronous<TResult>(invocation); } protected async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation) { TResult result; var isSuccess = true; OnBefore(invocation); try { invocation.Proceed(); var task = (Task<TResult>)invocation.ReturnValue; result = await task; } catch (Exception e) { isSuccess = false; OnException(invocation, e); throw; } finally { if (isSuccess) { OnSuccess(invocation); } } OnAfter(invocation); return result; } }
核心原因
你把OnBefore定义成了async void,这是异步编程的典型误区。async void方法抛出的异常无法被上层的try/catch捕获——因为这类方法不会返回可等待的Task,异常会直接抛到调用线程的同步上下文,完全脱离了你的拦截逻辑和异常中间件的捕获范围。
你的InternalInterceptAsynchronous方法只是调用了OnBefore(invocation),但没法等待async void方法执行完成,里面抛出的异常直接逃逸,根本没进入InternalInterceptAsynchronous的try/catch块,自然也到不了ExceptionMiddleware的捕获逻辑里。
解决步骤
1. 修改MethodInterception中的OnBefore签名
把返回类型从void改成Task,让它支持异步等待:
protected virtual Task OnBefore(IInvocation invocation) { return Task.CompletedTask; }
2. 更新拦截方法中的OnBefore调用
在所有异步拦截方法中await OnBefore(invocation),同步方法可以用同步等待的方式处理:
- 异步无返回值拦截方法:
protected async Task InternalInterceptAsynchronous(IInvocation invocation) { var isSuccess = true; await OnBefore(invocation); // 改为await等待异步操作完成 try { invocation.Proceed(); var task = (Task)invocation.ReturnValue; await task; } catch (Exception e) { isSuccess = false; OnException(invocation, e); throw; } finally { if (isSuccess) { OnSuccess(invocation); } } OnAfter(invocation); }
- 异步有返回值拦截方法同理:
protected async Task<TResult> InternalInterceptAsynchronous<TResult>(IInvocation invocation) { TResult result; var isSuccess = true; await OnBefore(invocation); // 改为await等待异步操作完成 try { invocation.Proceed(); var task = (Task<TResult>)invocation.ReturnValue; result = await task; } catch (Exception e) { isSuccess = false; OnException(invocation, e); throw; } finally { if (isSuccess) { OnSuccess(invocation); } } OnAfter(invocation); return result; }
- 同步拦截方法(如需支持异步OnBefore):
public override void InterceptSynchronous(IInvocation invocation) { var isSuccess = true; OnBefore(invocation).GetAwaiter().GetResult(); // 同步等待异步操作完成 try { invocation.Proceed(); } catch (Exception e) { isSuccess = false; OnException(invocation, e); throw; } finally { if (isSuccess) { OnSuccess(invocation); } } OnAfter(invocation); }
3. 修改SecuredOperation中的OnBefore实现
把返回类型改成Task,保留原有逻辑:
protected override async Task OnBefore(IInvocation invocation) { if (_httpcontextAccessor.HttpContext.User.Identity.IsAuthenticated) { if (_roles != null) { // some await proccess var result = await _fga.check(); if(result != null) return; throw new MemberAccessException(AuthMessages.AuthorizationForbidden); } return; } throw new UnauthorizedAccessException(AuthMessages.AuthorizationDenied); }
这样修改后,OnBefore中的异常会被InternalInterceptAsynchronous的try/catch捕获,重新抛出后就能被ExceptionMiddleware的捕获逻辑处理,触发HandleExceptionAsync方法。
内容的提问来源于stack exchange,提问作者Avora

