2012-08-15 35 views
2

我目前正在实现一个应用程序协议库依靠TCP/IP传输(持久连接)。事件v.s.使用TaskCompletionSource异步方法

我想实现一个很好的异步实现,它依赖于使用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对象来实现,并且所述TCS结果被设置在OnReceivedRequest该请求。而在客户端代码方面,代码不是订阅ReceivedRequestTypeA事件,而是由代码GetRequestTypeAAsync()组成。尽管客户端代码必须以某种方式提供一个响应库发送到服务器,我不知道这是如何工作的...

我的大脑现在完全模糊,不能真正想清楚。任何建议一个不错的设计将不胜感激。

谢谢!

回答

3

我也在致力于async/await TCP/IP套接字,我强烈建议你看看TPL Dataflow。使用两个BufferBlocks(一个用于读取,一个用于写入)制作async-友好的终端非常容易。

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

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

+0

不错!今天的另一个发现感谢您的回答! 我刚刚观看了来自Stephen Toub“TPL DataFlow Tour”的15分钟视频,它看起来非常整齐。想深入挖掘一下,看看我能如何将它应用到我的上下文中(感谢这些线索!)。顺便说一句为什么Dataflow不是.NET 4.5的一部分?它以某种方式间接暗示“稳定性”问题? – darkey 2012-08-16 01:26:22

+0

好吧,刚刚发现了我的评论问题的答案 http://blogs.msdn.com/b/bclteam/archive/2012/05/30/mef-and-tpl-dataflow-nuget-packages-for-net- framework-4-5-rc.aspx 再次感谢! – darkey 2012-08-16 01:29:47

+1

或者,如果你想分别处理每个消息类型,每一个都可以是一个单独的'Action'块,通过使用['LinkTo()'和谓词](http:// msdn)链接到解析'TrasnsformBlock'。 microsoft.com/en-us/library/hh194832.aspx),它根据消息的类型进行过滤。 – svick 2012-08-16 11:16:59