首页 > 解决方案 > TCP Socket ReceiveAsync 有时需要很长时间

问题描述

我必须使用一个设备,它使用 TCP 连接来控制它。它每 30 毫秒发送 1 个字节的数据,我必须尽快对其做出反应。通常,一切正常,但有时 Socket.ReceiveAsync() 函数会卡住长达 400-800 毫秒的时间,然后返回一些接收到的字节数。

我使用这样的代码:

_socket.ReceiveTimeout = 5;
var sw = Stopwatch.StartNew();
var len = await _socket.ReceiveAsync(new ArraySegment<byte>(Buffer, Offset, Count),
                                     SocketFlags.None)
                       .ConfigureAwait(false);
_logger.Info($"Reading took {sw.ElapsedMilliseconds}ms");  // usually 0-6ms, but sometimes up to 800ms

我用Wireshark记录了这个过程,我可以看到所有的数据都及时收到了,大约30ms的间隔。

我还注意到,当您在计算机上执行某些操作时,发生这种延迟的可能性更高。就像打开开始菜单或资源管理器一样。我认为,切换到另一个进程或垃圾收集应该更快。

标签: c#.netsocketsasync-await

解决方案


它每 30 毫秒发送 1 个字节的数据,我必须尽快对其做出反应。

这在不是实时操作系统的 Windows 上是极其困难的。确实,您所能做的就是尽力而为,了解意外的防病毒扫描可能会使其失败。

我曾经被派到一个客户站点调查仅在晚上发生的 TCP/IP 通信超时。飞机飞行,酒店住宿,整个shebang。原来夜班在玩《毁灭战士》。真实的故事。

所以,你不能真正保证这种应用程序会一直运行;你只需要尽力而为。

首先,我建议保持连续阅读。一直在阅读。从字面上看,一旦读取完成,将该字节推入生产者/消费者队列并尽快再次开始读取。不要为超时而烦恼;继续阅读。

您可以做的第二件事是减少开销。在这种情况下,您正在调用一个返回;的Socket API 。Task<T>最好调用一个返回ValueTask<T>

var len = await _socket.ReceiveAsync(
    new ArraySegment<byte>(Buffer, Offset, Count).AsMemory(), SocketFlags.None)
    .ConfigureAwait(false);

有关如何减少内存使用的更多信息,请参阅此博客文章ValueTask<T>,尤其是使用套接字。

或者,您可以使您的读取循环在单独的线程上同步运行,如评论中所述。单独线程的一个很好的方面是您可以提高它的优先级 - 甚至进入“实时”范围。但是有龙 - 你真的不想这样做,除非你真的别无选择。如果该线程上有任何错误,您可以死锁整个操作系统。

一旦你使你的读取循环尽可能紧凑(从不等待任何类型的处理),并减少你的内存分配(防止不必要的 GC),这就是你所能做的。总有一天,有人会启动《DOOM》,而你的应用程序将不得不尽力而为。


推荐阅读