简体   繁体   English

使用异步枚举同时在 .NET 中写入和读取 gRPC 全双工通道

[英]Writing and reading to/from gRPC full duplex channel in .NET simultaneously using async enumerables

I have a client-server scenario with a pipeline fully implemented with gRPC duplex streams.我有一个客户端-服务器场景,其中管道完全使用 gRPC 双工流实现。 The pipeline is initiated by the client.管道由客户端启动。 At every step (duplex call) the client reads items from an IAsyncEnumerable and writes them on the channel.在每一步(双工调用),客户端从 IAsyncEnumerable 读取项目并将它们写入通道。 The items are processed by the server and some of them are sent back on the channel asynchronously.这些项目由服务器处理,其中一些在通道上异步发送回。 Then the client returns the read items with yield.然后客户端返回带有产量的读取项目。 I have 4 methods chained up like that.我有 4 种方法像这样被链接起来。

The pattern I use:我使用的模式:

public async IAsyncEnumerable<Result> Results(IAsyncEnumerable<Input> inputs)
{
    var duplex = Server.GetResults();

    await foreach (var input in inputs)
    {
        await duplex.RequestStream.WriteAsync(input);
    }

    await duplex.RequestStream.CompleteAsync();

    await foreach (var result in duplex.ResponseStream.ToAsyncEnumerable())
    {
        yield return result;
    }
}

The problem with this pattern is that the server sends the data back before the second foreach (reading from the channel) kicks in. If there is a lot of data, the channel buffer becomes full (presumably) before the reading starts and an Operation cancelled exception is thrown by gRPC.这种模式的问题是服务器在第二次 foreach(从通道读取)开始之前将数据发回。如果有很多数据,则通道缓冲区在读取开始之前(可能)会变满并且操作被取消gRPC 抛出异常。

How can I refactor this without breaking the usage of IAsyncEnumerables and yield?如何在不破坏 IAsyncEnumerables 和产量的情况下重构它? If that is not possible, then how else can I achieve a "Write first, read next" scenario?如果这是不可能的,那么我还能如何实现“先写,后读”的场景?

Just for clarification, I cannot use the following pattern:只是为了澄清,我不能使用以下模式:

while(await stream.MoveNext()) 
{
   // send
   // yield
}

The moment I call await duplex.ResponseStream.MoveNext() the client waits for the server to return data.在我调用await duplex.ResponseStream.MoveNext()的那一刻,客户端等待服务器返回数据。 But that is unpredictable which data is being returned by the server.但这无法预测服务器正在返回哪些数据。 So that would cause a deadlock.所以会造成死锁。

I would be needing some kind of IsDataAvailableOnChannel but no such method exists on AsyncDuplexStreamingCall我需要某种IsDataAvailableOnChannelAsyncDuplexStreamingCall上不存在这种方法

You can hand off the sending of data to another task, then await it once you have finished yield ing.您可以将数据发送交给另一个任务,然后在完成yield后等待它。

Given that I assume you have a normal ThreadPool scheduler, the await of each function should be interleaved.假设我假设您有一个普通的 ThreadPool 调度程序,每个函数的await应该是交错的。

public async IAsyncEnumerable<Result> Results(IAsyncEnumerable<Input> inputs)
{
    var duplex = Server.GetResults();

    var sendingTask = Task.Run(async () =>
    {
        await foreach (var input in inputs)
        {
            await duplex.RequestStream.WriteAsync(input);
        }

        await duplex.RequestStream.CompleteAsync();
    });

    await foreach (var result in duplex.ResponseStream.ToAsyncEnumerable())
    {
        yield return result;
    }

    await sendingTask;
}

It might be easier to manage if you place the anonymous lambda into a proper function.如果将匿名 lambda 放入适当的函数中,可能会更容易管理。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM