使用基于任务的异步模式(TAP)的高效套接字I / O

时间:2013-06-25 20:16:37

标签: .net sockets task-parallel-library tpl-dataflow

我正在使用一个客户端应用程序,它使用原始TCP套接字与中央服务器进行通信。应用程序消息被序列化,然后以长度为前缀,以创建传递到TCP流的帧。

处理此问题的一种经典方法是直接调用套接字类上的Receive或BeginReceive,反序列化回调上的消息,将消息传递到单独的队列以供另一个线程处理,然后让回调开始另一个接收插座再次。

这种方法的天真实现对我来说并不理想 - 它将消息序列化和反序列化紧密地耦合到套接字,并且需要相当多的“管道”以使队列在不同的线程/回调中发挥良好性能。它也有点抽象 - 它需要调用代码知道底层套接字,而不是输入和输出消息的“数据流”。

鉴于我完全在.NET 4.5中工作,使用TPL(TaskFactory.FromAsync)包装Socket的Begin和End异步方法是一个显而易见的选择。但是,由于种种原因,我不清楚如何从这一点出发:

  1. 我需要一个永远不会完成接收数据的异步“任务”。只要连接套接字,我想要处理一组消息。任何中断(断开连接,套接字错误或取消请求)都将是一个例外,而不是传统的任务完成。根据Stephen Toub(http://blogs.msdn.com/b/pfxteam/archive/2011/10/02/10218999.aspx)的说法,我应该总是完成我的任务。这会产生一些问题 - 套接字接收永远不会在传统意义上完成。斯蒂芬似乎在他的“等待套接字操作”帖子中略微自相矛盾,他在那里显示了一个套接字读取,在没有套接字错误的情况下永远不会完成(http://blogs.msdn.com/b/pfxteam/archive/2011/12/15/10248293.aspx)。
  2. 我需要一种同步“排队”数据的方法。调用者应该能够在不阻塞的情况下发送要传输的消息,并且消息应该在套接字上顺序传输。换句话说,由于消息框架,在套接字本身上一次只发送一个。 TPL数据流是否合适,或者我应该使用不同的排队模式?
  3. 我想在消息序列化和消息传输之间清楚地分离关注点。
  4. 我没有看到很多这种策略的例子,只有“直接”的套接字I / O或简单的实现。我的直觉告诉我TPL Dataflow非常适合,因为序列化和反序列化可以流水线化。

    我不清楚如何将有效无穷无尽的接收任务链接到TPL Dataflow或类似的东西。

    有什么想法吗?

1 个答案:

答案 0 :(得分:2)

目前,还没有很好的方法。一旦你得到一个解决方案,我建议你把它发布到某个地方。

  

1。我需要一个永远不会完成接收数据的异步“任务”...

我不同意“套接字接收从未在传统意义上完成”的陈述。较低级别的“接收”操作(例如FromAsync / BeginReceive或Toub EndReceive周围的ReceiveAsync包装)会返回Task,此时会在那里完成是可用的数据,套接字是关闭的,还是有错误的。当套接字关闭或出现错误时,更高级别的“接收器”操作(例如,ReadAsync from Toub's post)将完成。

可以拥有可能需要无限期完成时间的Task,但只要他们就完成了。 Toub在this post中指出的是,您的Task应该完成最终(特别是处理错误情况)。这与Rx的方法不同,在这种方法中,拥有一个永远不会产生数据并且永不结束的可观察量是完全有效的。

  

2。我需要一种同步“排队”数据的方法......

从技术上讲,它是“序列化”(“按顺序”),而不是“同步”。理想的解决方案是异步序列化。有几种方法可以解决这个问题。

我想说TPL Dataflow非常适合。请注意,每个套接字实际上有两个逻辑“流”(读取和写入是独立的)。我使用基于Dataflow的套接字包装器取得了一些成功,但没有时间使其具有生产质量。由于两个流(每个可插拔的“块”必须有两个TPL数据流块,一个用于输入,一个用于输出),API相当笨拙。

另一种方法是Rx。 Rx具有比普通Task或TPL数据流更高的学习曲线,但提供了相当多的功率(和效率)。几年前我玩过基于Rx的插座,但从未有过任何工作。现在Rx的文档和示例要好得多,所以今天我认为它是一个可行的选择。

还有直接使用async方法的方法。您将使用同步而不是队列。例如,您可以使用SemaphoreSlim来确保只有一个SendAsync方法可以一次运行给定套接字。然而,这确实改变了语义,将一些“序列化”推到了你的调用代码:而不是简单的enqueue-and-complete-task(除非你点击你的发送限制,它总是会同步完成),你有异步等待发送然后完成任务(几乎总是异步等待)。您可以通过构建async生产者/消费者队列(like the one I wrote)来缓解此问题,但是您需要跟踪单独的消费者Task,并且那时你只是重写TPL数据流。

  

我不清楚如何将一个有效的“无限”接收任务链“桥接”到TPL Dataflow或类似的东西。

没有一种好的内置方法可以做到这一点。

一个简单的解决方案是拥有一个“Receiver”Task,它独自负责将数据推送到TPL Dataflow管道。但是,您必须监视Task以确保在发生某些错误时不会放弃管道,并且您需要一种方法来干净地关闭它。

我写了一个FuncBlock type来处理这种情况(想法是将它用于套接字读取和其他基于I / O的数据流输入)。花了一段时间来解决async方法和Dataflow块如何交互的语义(特别是在取消/错误/完成周围),但我认为它会很有用。我很感激任何反馈。