如何正确使用TPLClient的TPL?

时间:2017-04-08 23:21:42

标签: c# .net multithreading asynchronous tcpclient

我使用TcpListener编写了一个服务器,它应该处理数千个并发连接。

因为我知道大多数时候大多数连接都是空闲的(偶尔有乒乓以确保对方仍在那里)异步编程似乎是解决方案。

然而,在前几百个客户表现迅速恶化之后。事实上,我几乎无法达到1000个并发连接。

CPU未达到最大值(平均值为~4%),RAM使用率<100MB,并且网络流量不会很多。

当我在Visual Studio中暂停服务器并查看“任务”窗口时,有无数(数百个)任务状态为“已调度”且只有少数(少于30个)“正在运行/活动”任务。< / p>

我尝试使用Visual Studio以及dotTrace Peformacne进行配置文件,但我找不到任何错误。没有锁争用,没有使用大量CPU的“热路径”。 似乎应用程序整体上放慢了速度。

设置

我有一个简单的while(true),里面有这个:

var client = await tcpListener.AcceptTcpClientAsync().ConfigureAwait(false);
Task.Run(() => OnClient(client));

为了处理连接,我提出了一些方法来封装连接的不同阶段。 例如,在OnClient上面await HandleLogin(...),然后它进入while(client.IsConnected)循环,只执行await stream.ReadBuffer(1)stream只是从TcpClient.GetStream获得的普通NetworkStream,而ReadBuffer是这样实现的自定义方法:

public static async Task<byte[]> ReadBuffer(this Stream stream, int length)
{
    byte[] buffer = new byte[length];
    int read = 0;

    while (read < length)
    {
        int remaining = length - read;

        int readNow = await stream.ReadAsync(buffer, read, remaining).ConfigureAwait(false);
        read += readNow;

        if (readNow <= 0)
            throw new SocketException((int)SocketError.ConnectionReset);
    }

    return buffer;
}

我在await任何地方使用.ConfigureAwait(false),因为我需要任何类型的同步上下文,我不想支付retreiving /创建同步的性能开销无处不在。

我注意到的一件事是,当我从我的测试工具中生成50个连接然后随机关闭它(因此它所做的所有连接应该在服务器上接收到ConnectionReset SocketException)时,服务器需要很长时间才能做出反应通常都会完全挂起,直到新的连接到来。

难道某种延续想要以某种方式同步并在某个特定线程上运行吗? 有可能(在正确的时刻断开连接)使服务器应用程序几乎无法使用,只需20个连接。

我做错了什么? 如果它是一些错误(我认为它是),我将如何找到它? 我将问题缩小到只是坐在NetworkStream.ReadAsync(...)的许多任务,即使他们应该立即收到SocketException(ConnectionReset)。

我尝试在远程计算机和本地启动我的测试工具(只是使用TcpClient),我得到了相同的结果。

编辑1

我的OnClient定义为async Task OnClient(TcpClient client)。在其中,它等待连接的不同阶段:身份验证,一些设置协商,然后进入等待消息的循环。

我使用Task.Run因为我不想等到一个客户端完成,但我希望尽快接受所有客户端,为每个客户端生成一个新任务。然而,我不确定是否不能/不应该只在没有Task.Run的情况下编写OnClient(client)而且还没有等待OnClient(会导致提示不会消失但是这就是我想要的我想,我不想等到客户端完成。)

最后一个阶段

连接在验证后进入的最后阶段,设置协商是服务器等待来自客户端的消息的循环。 然而,在此之前,服务器还执行另一个Task.Run()(与while(已连接)并等待Task.Delay ...)发送ping数据包和一些其他“管理”事物。 通过使用Nito AsyncEx库中的锁定机制来同步对NetworkStream的所有写入,以确保没有数据包以某种方式交错。 如果在任何地方发生任何异常(在读或写时),我总是在TcpClient上调用.Close以确保所有其他未决的不完整读写都会抛出异常。

1 个答案:

答案 0 :(得分:3)

  

我将问题缩小到只是坐在NetworkStream.ReadAsync(...)的许多任务,即使他们应该立即收到SocketException(ConnectionReset)。

这是一个不正确的假设。 You have to write to the socket to detect dropped connections.

这是许多 TCP / IP编程的陷阱之一,这就是为什么我建议人们尽可能使用SignalR。

从您的代码/说明中跳出的其他陷阱:

  • 您尝试使用异步API,但您的代码也有OnClient。所以它仍在进行线程跳转。这可能是理想的,也可能不是。 (假设asyncwhile(client.IsConnected)方法;如果它使用sync-over-async,那么它肯定不是一个好的模式。)
  • IsConnected是一种常见的错误模式。您应该同时运行读循环和写队列处理器。特别是,IsConnected绝对没有意义 - 它实际上只意味着套接字已连接过去的某个点意味着仍然连接。如果代码有#include <stdio.h> #include <math.h> int inputData(int [][500]); //inputs the data and returns the rows by columns. int main(void){ int n = 0; int data[n][n]; printf("Grid size: %dx%d", inputData(data),inputData(data)); return 0; } int inputData(int data[][500]){ int i; int j; for(i = 1; i <= 500; i++){ for(j = 0; j <= 500; j++){ scanf("%d", &data[i][j]); } } return j; } ,则会出现错误。