从TcpClient接收数据时,递归调用异步方法与长时间运行的任务

时间:2015-11-01 22:34:02

标签: c# multithreading async-await tcpclient tcplistener

我目前正在使用StreamSocketListener将TCP服务器重写为TcpListener,因为我需要能够使用SSL。由于我是在不久前写的代码,我也试图让它更清晰,更容易阅读,并希望通过更多的客户端提高性能,但我现在卡住了。

我正在递归地调用一个接收方法,直到客户端断开连接,但我开始怀疑使用单个长时间运行的任务是否更好。但我不愿意使用它,因为它将为每个连接的客户端创建一个新的长期运行任务。这就是为什么我转向Stack Overflow社区获取有关如何继续的一些指导。

注意:对于大多数连接的客户端,连接应该是24/7全天候开放。

感谢任何评论。

当前代码如下所示:

test2

但我想知道是否应该使用类似下面的代码,并将其作为一个长期运行的任务启动:

private async Task ReceiveData(SocketStream socket) {
    await Task.Yield();
    try {
        using (var reader = new DataReader(socket.InputStream)) {
            uint received;
            do {
                received = await reader.LoadAsync(4);
                if (received == 0) return;
            } while (reader.UnconsumedBufferLength < 4);

            if (received == 0) return;

            var length = reader.ReadUInt32();
            do {
                received = await reader.LoadAsync(length);
                if (received == 0) return;
            } while (reader.UnconsumedBufferLength < length);

            if (received == 0) return;

            // Publish the data asynchronously using an event aggregator
            Console.WriteLine(reader.ReadString(length));
        }
        ReceiveData(socket);
    }
    catch (IOException ex) {
        // Client probably disconnected. Can check hresult to be sure.
    }
    catch (Exception ex) {
        Console.WriteLine(ex);
    }
}

1 个答案:

答案 0 :(得分:0)

在发布的第一个过度简化的代码版本中,&#34;递归&#34;方法没有异常处理。这本身就足以取消它的资格。但是,在您更新的代码示例中,您清楚地了解了async方法本身中的异常;因此,预计该方法不会抛出任何异常,因此无法await方法调用的问题就不那么重要了。

那么,我们还可以用什么来比较和对比这两个选项?

您写道:

  

我也试图让它更清洁,更容易阅读

虽然第一个版本并不是真正的递归,但是在每个对自身的调用会增加堆栈深度的意义上,它确实与真正的递归方法共享一些可读性和可维护性问题。对于有经验的程序员来说,理解这样的方法可能并不难,但如果不让他们在头上划伤一段时间,它至少会减慢经验不足的速度。

所以那就是。鉴于既定目标,这似乎是一个重大的劣势。

那么你写的第二个选项怎么样:

  

...然后它将为每个连接的客户端创建一个新的长时间运行任务

这是对如何运作的错误理解。

如果不深入研究async方法的工作原理,基本的行为是async方法实际上会在每次使用await时返回(忽略一会儿的可能性)同步完成的操作......假设典型情况是异步完成。

这意味着您使用以下代码行启动的任务:

Task.Factory.StartNew(
    async delegate { await ReceiveData(_socket); },
    TaskCreationOptions.LongRunning);

...只有足够长的时间才能到达await方法中的第一个ReceiveData()。此时,该方法返回并且启动的任务终止(允许线程完全终止,或者返回到线程池,具体取决于任务调度程序决定运行任务的方式)。

没有&#34;长时间运行的任务&#34;对于每个连接的客户端,至少不是在线程被用完的意义上。 (从某种意义上说,当然还有&#39; sa Task对象。但这对于&#34;递归&#34;方法同样如此循环方法。)


那就是技术比较。当然,由您来决定对您自己的代码有什么影响。但无论如何,我会提出自己的意见......

对我来说,第二种方法更具可读性。这是因为asyncawait的设计方式以及为什么设计的方式。也就是说,C#中的这个特性特别允许异步代码以几乎与常规同步代码完全相同的方式实现。事实上,这是错误的印象,即有一个长期运行的任务&#34;致力于每个连接。

async / await功能之前,编写可扩展网络实现的正确方法是使用"Asynchronous Programming Model"中的一个API。在此模型中,IOCP线程池用于服务I / O完成,这样少量线程可以监视并响应大量连接。

切换到新的async / await语法时,实际不会更改的基础实现细节。该进程仍然使用少量IOCP线程池线程来处理发生的I / O完成。

不同之处在于,当使用async / await时,代码看起来就像每个连接使用单个线程时所编写的代码类型(因此误解了这段代码的实际效果。它只是一个很大的循环,在一个地方进行了所有必要的处理,无需使用不同的代码来启动I / O操作并完成一个操作(即调用Begin...()以及稍后调用End...())。

对我来说,这就是async / await的美丽,以及为什么您的代码的第一个版本不如第二个版本。第一个没有利用使async / await对代码有用和有益的原因。第二个充分利用了这一点。


当然,美丽是旁观者的眼睛。并且至少在有限的程度上,可读性和可维护性也是如此。但鉴于您声明的目标是使代码更清晰,更易于阅读,我觉得您的代码的第二个版本最适合这个目的。

假设现在代码是正确的,那么您将更容易阅读(并且记住,今天不仅仅是您需要阅读它......您希望它可以阅读&#34;您&#34 ;从现在起一年后,你还没有看到代码一段时间了。如果事实证明代码正确,那么更简单的第二版将更容易阅读,调试和修复。

这两个版本实际上几乎相同。在这方面,它几乎无关紧要。但是代码越少越好。第一个版本有两个额外的方法调用和悬空等待操作,而第二个版本用简单的while循环替换它们。我知道哪一个发现更具可读性。 :)


相关问题/有用的附加阅读:
Long Running Blocking Methods. Difference between Blocking, Sleeping, Begin/End and Async
Is async recursion safe in C# (async ctp/.net 4.5)?