事件v.s.使用TaskCompletionSource的异步方法

时间:2012-08-15 23:42:42

标签: c# async-await

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

我正在尝试使用C#5异步/等待构造来实现依赖于TAP模式的良好异步实现,主要是为了实践到目前为止我在理论上看到的概念。

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

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

var rsp = await session.SendRequestAsync(req);

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

这部分似乎很好。

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

我的协议库使用异步循环来监听底层流(接收来自服务器的传入响应或请求)。 此循环在流上异步读取响应/请求,然后在来自服务器的请求的情况下,它引发与请求类型相对应的事件(例如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)。

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

我在考虑不是使用事件,而是使用异步方法GetRequestTypeAAsync(),它可以使用库中的TaskCompletionSource对象实现,而tcs结果是使用{{1}中的请求设置的}}。在客户端代码方面,代码不是订阅OnReceivedRequest事件,而是由循环ReceivedRequestTypeA组成。仍然因为客户端代码必须以某种方式提供对要发送到服务器的库的响应,我不知道这是如何工作的......

我的大脑现在完全模糊,无法真正清醒。任何有关精美设计的建议都将受到高度赞赏。

谢谢!

1 个答案:

答案 0 :(得分:3)

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

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

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