我有以下案例。客户端使用客户端流(通过 grpc-dotnet)将文件上传到服务器。在某些情况下,服务器可能决定忽略客户端流并立即返回响应。由于客户端不知道服务器的决定,因此它继续调用 RequestStream.WriteAsync() 并最终得到
Grpc.Core.RpcException: 'Status(StatusCode="OK", Detail="")'
问题是:是否有一种优雅的方式可以提前知道调用已经完成,而不会进行错误的写入尝试?
浏览 grpc-dotnet 源代码,我设法通过反射
AsyncClientStreamingCall.RequestStream.Call.ResponseFinished
标志找到了解决方案,但它看起来比使用已知 StatusCode 处理异常更糟糕。
客户端伪代码:
private static async Task CallUploadStream(GrpcService.GrpcServiceClient client)
{
using var streamingCall = client.UploadStream();
// First call
await WriteAsync();
// Delay
await Task.Delay(TimeSpan.FromSeconds(1));
// Second call
await WriteAsync(); // Grpc.Core.RpcException: 'Status(StatusCode="OK", Detail="")'
await streamingCall.RequestStream.CompleteAsync();
await streamingCall;
return;
Task WriteAsync()
{
return streamingCall.RequestStream.WriteAsync(
new UploadStreamRequest
{
Bytes = ByteString.CopyFrom(new byte[1]),
}
);
}
}
Protobuf 合约:
syntax = "proto3";
package grpc.debug.contract.v1;
option csharp_namespace = "GrpcDebug.Contract";
import "google/protobuf/empty.proto";
service GrpcService {
rpc UploadStream(stream UploadStreamRequest) returns (google.protobuf.Empty);
}
message UploadStreamRequest { bytes bytes = 1; }
立即返回响应的服务器:
public class GrpcServiceV1 : GrpcService.GrpcServiceBase
{
public override Task<Empty> UploadStream(IAsyncStreamReader<UploadStreamRequest> requestStream,
ServerCallContext context)
{
return Task.FromResult(new Empty());
}
}
所以要明确一点:对
WriteAsync
的调用是否会因RpcException
而出错?您可以在每次写入之前尝试检查call.ResponseAsync.IsCompleted
(或者可能是为了获取下一个块而进行的每一项工作),但这本质上是一种竞争条件,您必须始终准备好捕获异常即使您检查之前的行。如果您在这里模拟的是基本上一个Stream
,或者至少:您正在以多个块发送大量有效负载,那么您可能也对 protobuf-net.Grpc 工作感兴趣,以添加二进制流原生支持;在 NuGet 的版本中,目前仅限于服务器 returning a Stream
,但这主要是因为这是我有一个消费者迫切需要的场景 - 计划是添加所有方向组合,并支持除Stream
,如Pipe
等