ASP.NET Core中间件中必须用 async
+ await
,不能只写 async
方法但同步调用
中间件委托签名是
Func<httpcontext requestdelegate task></httpcontext>,返回类型必须是
Task。如果方法声明为
async Task却没用
await,编译器会警告,运行时可能阻塞线程或丢弃未等待的
Task。 错误写法:
app.Use(async (context, next) =>
{
SomeAsyncOperation(); // 忘了 await → 返回 void,中间件立即往下走
await next();
});
正确写法:app.Use(async (context, next) =>
{
await SomeAsyncOperation(); // 真正 await
await next();
});
若异步操作无依赖后续逻辑,可用 ConfigureAwait(false)避免上下文捕获(尤其在非 UI 场景)
next()
本身必须 await
,否则请求流程中断
next()是下一个中间件的入口,它返回
Task。不
await它会导致当前中间件“假性完成”,后续中间件可能没执行,响应也可能提前结束或空内容。 典型现象:HTTP 200 状态码返回,但响应体为空,或日志显示
next()后的代码已执行,但浏览器收不到数据 即使你只做日志或监控,也得
await next(),否则请求生命周期被截断 想在下游执行完后补逻辑?把代码放在
await next()后面即可:
app.Use(async (context, next) =>
{
var sw = Stopwatch.StartNew();
await next(); // 等下游全部跑完
sw.Stop();
_logger.LogInformation("Total time: {Elapsed}", sw.ElapsedMilliseconds);
});
避免在中间件里用 Task.Run
包装同步 I/O 操作
有人误以为“加个
Task.Run就是异步”,但在 ASP.NET Core 中,这反而增加线程调度开销,还可能耗尽线程池。真正的异步应来自底层支持(如
Stream.ReadAsync、
HttpClient.GetAsync、EF Core 的
ToListAsync)。 反模式:
app.Use(async (context, next) =>
{
await Task.Run(() => {
var data = File.ReadAllText("config.json"); // 同步读文件 → 假异步
});
await next();
});
正解:改用真正异步 API:await using var stream = File.OpenRead("config.json");
using var reader = new StreamReader(stream);
var content = await reader.ReadToEndAsync();
注意:File.ReadAllTextAsync在 .NET 6+ 才有;旧版本需手动构造
FileStream+
StreamReader
异常处理要覆盖整个 await
链,别漏掉 next()
中间件里
try/catch只包自己代码,不包
await next(),那下游抛的异常就逃逸出去了,可能触发全局 500 且没日志。 常见疏忽:
try
{
await DoSomethingAsync();
// 忘了把 await next() 放进 try 块!
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed in middleware");
}
安全写法:try
{
await DoSomethingAsync();
await next(); // 这句也要在 try 内
}
catch (Exception ex)
{
_logger.LogError(ex, "Middleware failed");
throw; // 或写响应,但别静默吞掉
}
更推荐用全局异常处理中间件(UseExceptionHandler),但自定义中间件内仍需确保
await next()不裸奔 中间件异步的核心就一条:所有
Task返回值的操作,只要语义上需要等它完成,就必须
await——包括你自己的调用,也包括
next()。漏掉任何一个,都可能让请求流断裂、资源泄漏或行为不可预测。
