具有期限和取消功能的可靠 gRPC 服務
注意
這不是這篇文章的最新版本。 如需目前版本,請參閱本文的 .NET 8 版本。
警告
不再支援此版本的 ASP.NET Core。 如需詳細資訊,請參閱 .NET 和 .NET Core 支援原則。 如需目前版本,請參閱本文的 .NET 8 版本。
期限和取消是 gRPC 用戶端用於中止進行中呼叫的功能。 本文討論期限和取消功能的重要性,以及如何在 .NET gRPC 應用程式中使用這些功能。
期限
期限可讓 gRPC 用戶端指定等待呼叫完成的時間長度。 超過期限時,會取消呼叫。 期限會提供呼叫的執行時間上限,因此設定期限很重要。 它可停止運作異常的服務,避免其無止盡地執行並耗盡伺服器資源。 期限是建置可靠應用程式的實用工具,因此應該設定期限。
期限組態:
- 期限是進行呼叫時使用
CallOptions.Deadline
來設定。 - 沒有預設的期限值。 除非指定期限,否則 gRPC 呼叫沒有時間限制。
- 期限是指超過期限的 UTC 時間。 例如,
DateTime.UtcNow.AddSeconds(5)
是從現在起過 5 秒截止的期限。 - 如果使用過去或目前的時間,呼叫會立即超過期限。
- 期限會以 gRPC 呼叫傳送至服務,並由用戶端和服務各自獨立追蹤。 gRPC 呼叫有可能在一部電腦上完成,但回應傳回給用戶端時已超過期限。
如果超過期限,用戶端和服務的行為將有所不同:
- 用戶端會立即中止基礎 HTTP 要求並擲回
DeadlineExceeded
錯誤。 用戶端應用程式可以選擇攔截錯誤,並且向使用者顯示逾時訊息。 - 在伺服器上,執行中的 HTTP 要求中止並引發 ServerCallCoNtext.CancellationToken。 雖然 HTTP 要求已中止,但 gRPC 呼叫仍會繼續在伺服器上執行,直到方法完成為止。 請務必將取消權杖傳遞至非同步方法,使其隨呼叫一起取消。 例如,將取消權杖傳遞至非同步資料庫查詢和 HTTP 要求。 傳遞取消權杖可讓取消的呼叫在伺服器上快速完成,並釋放資源給其他呼叫使用。
設定 CallOptions.Deadline
以設定 gRPC 呼叫的期限:
var client = new Greet.GreeterClient(channel);
try
{
var response = await client.SayHelloAsync(
new HelloRequest { Name = "World" },
deadline: DateTime.UtcNow.AddSeconds(5));
// Greeting: Hello World
Console.WriteLine("Greeting: " + response.Message);
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.DeadlineExceeded)
{
Console.WriteLine("Greeting timeout.");
}
在 gRPC 服務中使用 ServerCallContext.CancellationToken
:
public override async Task<HelloReply> SayHello(HelloRequest request,
ServerCallContext context)
{
var user = await _databaseContext.GetUserAsync(request.Name,
context.CancellationToken);
return new HelloReply { Message = "Hello " + user.DisplayName };
}
期限和重試
若 gRPC 呼叫設定了重試錯誤處理和期限,該期限會追蹤 gRPC 呼叫的所有重試時間。 如果超過期限,gRPC 呼叫會立刻中止基礎 HTTP 要求、略過任何剩餘的重試,並擲回 DeadlineExceeded
錯誤。
散佈期限
從執行中的 gRPC 服務進行 gRPC 呼叫時,應該散佈期限。 例如:
- 用戶端應用程式呼叫
FrontendService.GetUser
並指定期限。 FrontendService
會呼叫UserService.GetUser
。 用戶端指定的期限應該在新的 gRPC 呼叫中指定。UserService.GetUser
接收期限。 如果超過用戶端應用程式的期限,就會如預期地逾時。
呼叫內容會提供期限:ServerCallContext.Deadline
public override async Task<UserResponse> GetUser(UserRequest request,
ServerCallContext context)
{
var client = new User.UserServiceClient(_channel);
var response = await client.GetUserAsync(
new UserRequest { Id = request.Id },
deadline: context.Deadline);
return response;
}
手動散佈期限可能很麻煩。 必須將期限傳遞給每個呼叫,一不小心就很容易遺漏。 gRPC 用戶端處理站提供自動化解決方案。 指定 EnableCallContextPropagation
:
- 自動將期限和取消權杖散佈至子呼叫。
- 如果子呼叫指定較短的期限,就不會散佈期限。 例如,如果子呼叫使用
CallOptions.Deadline
指定 5 秒的新期限,則不會使用散佈的 10 秒期限。 有多個可用期限時,會使用最短的期限。 - 這是確保複雜的巢狀 gRPC 案例絕對會散佈期限和取消權杖的絕佳方式。
services
.AddGrpcClient<User.UserServiceClient>(o =>
{
o.Address = new Uri("https://localhost:5001");
})
.EnableCallContextPropagation();
如需詳細資訊,請參閱 .NET 中的 gRPC 用戶端處理站整合。
取消
「取消」可讓 gRPC 用戶端取消不再需要的長時間執行呼叫。 例如,當使用者瀏覽網站上的頁面,串流即時更新的 gRPC 呼叫便會啟動。 一旦使用者離開頁面時,串流應該就會取消。
使用 CallOptions.CancellationToken 傳遞取消權杖或在呼叫中呼叫 Dispose
,即可取消用戶端中的 gRPC 呼叫。
private AsyncServerStreamingCall<HelloReply> _call;
public void StartStream()
{
_call = client.SayHellos(new HelloRequest { Name = "World" });
// Read response in background task.
_ = Task.Run(async () =>
{
await foreach (var response in _call.ResponseStream.ReadAllAsync())
{
Console.WriteLine("Greeting: " + response.Message);
}
});
}
public void StopStream()
{
_call.Dispose();
}
可以取消的 gRPC 服務應該:
- 將
ServerCallContext.CancellationToken
傳遞到非同步方法。 取消非同步方法可讓伺服器上的呼叫快速完成。 - 將取消權杖散佈至子呼叫。 散佈取消權杖可確保子呼叫會與父代一起取消。 gRPC 用戶端處理站 和
EnableCallContextPropagation()
會自動散佈取消權杖。