在Async / Await TCP服务器中断开套接字后的大量时间延迟

时间:2016-03-15 09:42:59

标签: c# sockets asynchronous tcp async-await

我一直在使用C#中的Begin / End模式升级某些较旧的软件,以使用async类的新TcpClient功能。

长话短说,这种接收方法适用于少量连接套接字,并且继续适用于10,000多个连接。当这些插座断开时会出现问题。

我使用服务器端的方法本质上是这个(大大简化但仍会导致问题):

private async void ReceiveDataUntilStopped(object state)
        {
            while (IsConnected)
            {
                try
                {
                    byte[] data = new byte[8192];
                    int recvCount = await _stream.ReadAsync(data, 0, data.Length);
                    if (recvCount == 0) { throw new Exception(); }
                    Array.Resize(ref data, recvCount);
                    Console.WriteLine(">>{0}<<", Encoding.UTF8.GetString(data));
                }
                catch { Shutdown(); return; }
            }
        }

当接受连接时,使用ThreadPool.QueueUserWorkItem(ReceiveDataUntilStopped);调用此方法。

要测试服务器,我连接了1,000个套接字。接受这些的时间可以忽略不计,大约2秒钟左右。我对此非常满意。但是,当我断开这些1,000个套接字时,该过程需要花费大量时间(15秒或更长时间)来处理这些套接字的关闭(Shutdown方法)。在此期间,我的服务器拒绝任何更多连接。我清空Shutdown方法的内容,看看是否有阻塞的内容,但延迟仍然相同。

我是愚蠢的做某事我不应该吗?我对async / await模式比较新,但到目前为止还很享受。

这是不可避免的行为吗?我知道在生产中不太可能同时断开1000个插槽,但我希望能够在不导致拒绝服务的情况下处理这样的情况。听起来很奇怪听众停止接受新套接字,但我希望这是因为所有ThreadPool线程都在忙着关闭断开连接的套接字?

编辑:虽然我同意在收到0字节时抛出异常并不是一个好的控制流程,但是问题的根源。简单if (recvCount == 0) { Shutdown(); return; }仍然存在问题。这是因为如果另一方不清楚地断开连接,ReadAsync会抛出IOException。我也意识到我没有正确处理缓冲区等等。这只是一个例子,内容很少,就像SO喜欢的那样。我使用以下代码接受客户:

private async void AcceptClientsUntilStopped()
        {
            while (IsListening)
            {
                try
                {
                    ServerConnection newConnection = new ServerConnection(await _listener.AcceptTcpClientAsync());
                    lock (_connections) { _connections.Add(newConnection); }
                    Console.WriteLine(_connections.Count);
                }
                catch { Stop(); }
            }
        }

2 个答案:

答案 0 :(得分:2)

if (recvCount == 0) { throw new Exception(); }

如果断开连接,则抛出异常。例外是非常昂贵的。我以10000 /秒的速度对它们进行一次基准测试。这很慢。

在调试器下,异常再次大大减慢(可能是100倍)。

这是对控制流的异常的滥用。从代码质量的角度来看,这非常糟糕。您的异常处理也非常糟糕,因为它捕获的太多了。您意味着来解决套接字问题,但您也会吞下所有可能的错误,例如NRE。

        using (mySocket) { //whatever you are using, maybe a TcpClient
        while (true)
        {
                byte[] data = new byte[8192];
                int recvCount = await _stream.ReadAsync(data, 0, data.Length);
                if (recvCount == 0) break;
                Array.Resize(ref data, recvCount);
                Console.WriteLine(">>{0}<<", Encoding.UTF8.GetString(data));
        }
        Shutdown();
        }

好多了,哇。

其他问题:缓冲区处理效率低,UTF8解码失败(无法在任何字节位置拆分UTF8!),使用async void(可能应该使用Task.Run启动此方法,或者只是调用它并丢弃结果任务)。

在评论中我们发现以下工作:

启动一个高prio线程并同步接受(无等待)。这应该保持接受。修复异常不是100%可能的,但是:await会增加异常的成本,因为它会重新抛出异常。它使用ExceptionDispatchInfo来保存进程全局锁。可能是您的可伸缩性问题的一部分。您可以通过等待readTask.ContinueWith(_ => { })来提高性能。等待永远不会抛出。

答案 1 :(得分:0)

基于提供的代码和我对问题的初步了解。我认为你应该采取一些措施来解决这个问题。

  1. 使用async Task代替async void。这将确保async状态机知道如何实际维护其状态。
  2. 而不是在ThreadPool.QueueUserWorkItem(ReceiveDataUntilStopped);方法的上下文中通过ReceiveDataUntilStopped调用await ReceiveDataUntilStopped来电async Task
  3. 使用async awaitTaskTask<T>个对象代表异步操作。如果您担心在原始调用线程上执行await的结果,则可以使用.ConfigureAwait(false)来阻止捕获当前的同步上下文。这个问题解释得非常好herehere too

    此外,看一下类似的&#34; read-while&#34;写得with this example