gpt4 book ai didi

c# - 事件对使用 TaskCompletionSource 的异步方法

转载 作者:行者123 更新时间:2023-11-30 22:26:14 27 4
gpt4 key购买 nike

我目前正在实现一个依赖 TCP/IP 进行传输(持久连接)的应用程序协议(protocol)库。

我正在尝试使用 C#5 async/await 构造依赖于 TAP 模式来实现一个不错的异步实现,主要是为了将我迄今为止只在理论上看到的概念付诸实践。

客户端可以连接到远程服务器并向其发送请求。
客户端接收来自服务器的响应以及请求(全双工模式)。

从客户端代码的角度来看,对我的库的异步调用以向服务器发送请求并接收相关响应非常简单:

var rsp = await session.SendRequestAsync(req);

在我的协议(protocol)库中,我只是在构建请求,将其转换为字节(要在网络流上发送),然后我调用 WriteAsync在流上,然后我等待在发送请求之前创建的任务,使用 TaskCompletionSource对象,它基本上是等待接收到关联的响应(并在 tcs 上设置结果),然后将响应返回给客户端调用者。

这部分看起来不错。

现在“问题”涉及服务器向客户端发送请求的部分。服务器可以向客户端发送不同类型的请求。

我的协议(protocol)库使用异步循环来监听底层流(接收来自服务器的传入响应或请求)。
这个循环在流上异步读取响应/请求,然后在来自服务器的请求的情况下,它会引发与请求类型(例如 ReceivedRequestTypeA)相对应的事件。客户端代码可以订阅这些事件,以便在从服务器接收到特定请求类型时得到通知。这些事件参数包含与请求相关的所有参数以及响应对象,由客户端设置,一旦事件处理程序代码完成,它将由库异步发送到流上。

异步监听循环的代码如下。请不要介意 while true ,不是很漂亮(应该使用取消模式),但这不是重点!
private async Task ListenAsync()
{
while(true)
{
Request req = await ReadRequestAsync();
await OnReceivedRequest(req);
}
}

所以循环调用异步方法 ReadRequestAsync这只是在流中异步读取一些字节,直到完整的请求或响应可用。
然后它将请求转发到异步方法 OnReceivedRequest可以在下面看到哪些代码:
private async Task OnReceivedRequest(Request req)
{
var eventArgs = new ReceivedRequestEventArgs { Req = req };

if (req is RequestTypeA)
{ ReceivedRequestTypeA(this, eventArgs); }

[... Other request types ...]

await SendResponseAsync(eventArgs.Resp);
}

此异步方法引发适当的请求类型事件。
客户端代码订阅了此事件,因此调用其相应的事件处理程序方法......客户端代码对请求执行任何所需的操作,然后构造响应并将其设置在 EventArgs 对象中 - 事件处理程序方法的结尾 -。代码在库中的 OnReceivedRequest 中恢复,并异步发送响应(在底层流上调用 WriteAsync)。

我认为这不是一个好方法,因为如果客户端的事件处理程序代码正在执行冗长的阻塞操作,它可以完全阻塞库中的异步循环(再见完全异步协议(protocol)库,你现在正在以某种方式同步由于客户端代码)。如果我使用基于异步任务的委托(delegate)来处理事件并等待它,也会发生同样的情况。

我在想,我可以有一个异步方法 GetRequestTypeAAsync(),而不是使用事件。这将使用 TaskCompletionSource 来实现库中的对象,并使用 OnReceivedRequest 中的请求设置 tcs 结果.在客户端代码方面,而不是订阅 ReceivedRequestTypeA事件,代码宁愿包含一个循环环绕 GetRequestTypeAAsync() .仍然由于客户端代码必须以某种方式提供对要发送到服务器的库的响应,我不知道这是如何工作的......

我的大脑现在完全模糊,无法真正思考清楚。任何关于漂亮设计的建议将不胜感激。

谢谢 !

最佳答案

我也在研究 async/await TCP/IP 套接字,我强烈建议您查看 TPL Dataflow。制作很容易async - 使用两个 BufferBlock 的友好端点s(一个用于读取,一个用于写入)。

在我的系统中,TCP/IP 套接字包装器公开了一个简单的 ISourceBlock<ArraySegment<byte>>代表原始读数。然后链接到 TransformManyBlock执行message framing ,从那里可以链接到 TransformBlock它将字节解析为实际的消息实例。

如果您有 RequestType,则此方法效果最佳。所有其他消息类型都继承自的基类。然后你可以有一个接收任务,它只是(异步)接收RequestType。来自数据流管道末端的消息实例。

关于c# - 事件对使用 TaskCompletionSource 的异步方法,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/11978846/

27 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com