【问题标题】:events v.s. async methods using TaskCompletionSource事件对使用 TaskCompletionSource 的异步方法
【发布时间】:2012-08-16 08:37:59
【问题描述】:

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

我正在尝试使用 C#5 async/await 构造依赖于 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 对象来实现,并使用OnReceivedRequest 中的请求设置tcs 结果。而在客户端代码方面,不是订阅ReceivedRequestTypeA 事件,而是代码宁愿包含一个围绕GetRequestTypeAAsync() 的循环。仍然由于客户端代码必须以某种方式向要发送到服务器的库提供响应,我不知道这是如何工作的......

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

谢谢!

【问题讨论】:

    标签: c# async-await


    【解决方案1】:

    我也在研究async/await TCP/IP 套接字,我强烈建议您看看 TPL 数据流。使用两个BufferBlocks(一个用于读取,一个用于写入)创建async 友好的端点非常容易。

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

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

    【讨论】:

    • 不错!今天又有一个发现。感谢您的回答,+1 !我刚刚观看了 Stephen Toub 的 15 分钟视频“TPL DataFlow Tour”,它看起来非常非常简洁。将深入研究它,看看我如何在我的上下文中应用它(感谢领导!)。顺便说一句,知道为什么 Dataflow 不是 .NET 4.5 的一部分吗?它是否以某种方式间接暗示“稳定性”问题?
    • 好的,刚刚找到我的评论问题的答案blogs.msdn.com/b/bclteam/archive/2012/05/30/…再次感谢!
    • 或者,如果您想分别处理每种消息类型,每个消息类型都可以是一个单独的 Action 块,使用基于类型过滤的 LinkTo() with a predicate 链接到解析 TrasnsformBlock消息。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多