截止时间、取消与拦截器
使用截止时间应用超时,传递 CancellationToken,并通过 gRPC 拦截器添加横切关注点。
截止时间、取消与拦截器 是 CoddyKit 上的免费 C# Academy 课时。 这是第 4 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
gRPC 的可靠性:截止时间与取消
生产环境中的 gRPC 服务必须处理两个关键的可靠性问题:截止时间(client 会等待多长时间)和取消(提前停止工作)。二者结合可以防止资源耗尽和级联故障。
为 call 设置截止时间
截止时间是 call 必须完成的绝对时间点。请使用 CallOptions 进行设置。如果超过该时间,服务器会收到取消信号,而 client 会得到 StatusCode.DeadlineExceeded。
var deadline = DateTime.UtcNow.AddSeconds(5);
var reply = await client.GetOrderAsync(
new GetOrderRequest { Id = 42 },
deadline: deadline);
// Or via CallOptions:
var options = new CallOptions(deadline: deadline);
var reply2 = await client.GetOrderAsync(
new GetOrderRequest { Id = 42 }, options);在 Client 上处理 DeadlineExceeded
捕获 RpcException 并检查状态码,以区分截止时间到期和其他错误。
try
{
var reply = await client.GetOrderAsync(
new GetOrderRequest { Id = 42 },
deadline: DateTime.UtcNow.AddSeconds(3));
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
{
Console.WriteLine("Call timed out");
}
catch (RpcException ex)
{
Console.WriteLine($"gRPC error: {ex.Status.Detail}");
}在服务器上遵守截止时间
服务器应在长时间工作期间检查 context.CancellationToken。截止时间到达后,该令牌会自动被取消。
public override async Task<ReportResponse> GenerateReport(
ReportRequest request,
ServerCallContext context)
{
var ct = context.CancellationToken;
// Pass ct to every async call — work stops when deadline hits
var data = await _db.FetchLargeDatasetAsync(request.Filters, ct);
var report = await _reportEngine.BuildAsync(data, ct);
ct.ThrowIfCancellationRequested(); // explicit check before CPU work
return MapToResponse(report);
}CancellationToken 传播
始终通过每一层 async call 链传播服务器的取消令牌。如果忽略它,被取消的 calls 仍会不必要地消耗资源。
// GOOD: token flows through the call chain
await _db.Products.AsNoTracking()
.Where(p => p.IsActive)
.ToListAsync(context.CancellationToken); // cancels if client disconnects
// BAD: ignores cancellation
await _db.Products.ToListAsync(); // keeps running after deadlinegRPC 拦截器:概念
拦截器相当于 gRPC 中的 ASP.NET Core 中间件:它们在 RPC call 之前或之后运行,并可以检查或修改请求、响应和异常。它们非常适合用于日志记录、身份验证和重试逻辑。
创建服务器端拦截器
继承 Interceptor,并重写所需拦截的方法类型。调用 continuation 以继续执行实际的 handler。
using Grpc.Core.Interceptors;
public class LoggingInterceptor : Interceptor
{
private readonly ILogger<LoggingInterceptor> _logger;
public LoggingInterceptor(ILogger<LoggingInterceptor> l) => _logger = l;
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
_logger.LogInformation("gRPC call: {Method}", context.Method);
var sw = System.Diagnostics.Stopwatch.StartNew();
var response = await continuation(request, context);
_logger.LogInformation("Completed in {Ms}ms", sw.ElapsedMilliseconds);
return response;
}
}注册服务器端拦截器
可以在 Program.cs 中全局注册拦截器(应用于所有服务),也可以按服务注册。DI 容器会解析构造函数依赖项。
// Globally for all gRPC services:
builder.Services.AddGrpc(opt =>
opt.Interceptors.Add<LoggingInterceptor>());
// Or per service:
builder.Services.AddGrpc();
builder.Services.AddSingleton<LoggingInterceptor>();
app.MapGrpcService<OrderService>()
.AddInterceptor<LoggingInterceptor>();Client 端拦截器
Client 拦截器包装传出的 call,可用于添加身份验证标头、重试逻辑或关联 ID,而无需修改每个调用位置。
public class AuthInterceptor : Interceptor
{
private readonly ITokenProvider _tokens;
public AuthInterceptor(ITokenProvider t) => _tokens = t;
public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
TRequest request,
ClientInterceptorContext<TRequest, TResponse> context,
AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
var headers = context.Options.Headers ?? new Metadata();
headers.Add("authorization", $"Bearer {_tokens.GetToken()}");
var newContext = new ClientInterceptorContext<TRequest, TResponse>(
context.Method, context.Host,
context.Options.WithHeaders(headers));
return continuation(request, newContext);
}
}
// Attach to channel:
var invoker = channel.Intercept(new AuthInterceptor(tokenProvider));
var client = new OrderService.OrderServiceClient(invoker);异常拦截器
服务器端异常拦截器会将未处理的异常转换为 RpcExceptions,防止内部细节泄露给 client。
public class ExceptionInterceptor : Interceptor
{
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
try
{
return await continuation(request, context);
}
catch (NotFoundException ex)
{
throw new RpcException(new Status(StatusCode.NotFound, ex.Message));
}
catch (Exception ex)
{
throw new RpcException(new Status(StatusCode.Internal, "Internal error"));
}
}
}快速检查
当 gRPC call 超过其截止时间时会发生什么?
回顾:截止时间、取消与拦截器
要点:
- 始终为 client call 设置截止时间,以防止无限期等待
- 在服务器上检查
context.CancellationToken,并将其传递给所有 async call - 拦截器负责处理横切关注点(日志记录、身份验证和错误映射),避免服务代码变得杂乱
- 服务器端拦截器:重写一元或流式 handler 方法,并调用
continuation - Client 端拦截器:附加到 channel 调用器链
常见问题解答
「截止时间、取消与拦截器」课时是免费的吗?
是的 — 「截止时间、取消与拦截器」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「截止时间、取消与拦截器」这节课中我会学到什么?
使用截止时间应用超时,传递 CancellationToken,并通过 gRPC 拦截器添加横切关注点。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 4 节课,共 4 节。
「截止时间、取消与拦截器」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。
此课程中的所有课时
- gRPC 与 Protobuf 基础
- 一元与服务器流式 RPC
- 客户端与双向流式传输
- 截止时间、取消与拦截器