.NET 的异步套接字似乎与线程池有问题

Posted

技术标签:

【中文标题】.NET 的异步套接字似乎与线程池有问题【英文标题】:Asynchronous Sockets with .NET seem to have an issue with the thread pool 【发布时间】:2014-09-12 06:16:38 【问题描述】:

我注意到下面链接的异步套接字发送和接收表单 MSDN 的示例不起作用。发生的情况是,在使用 BeginReceive 在 Receive 中创建的线程直到在 StartClient 方法上运行的线程退出该方法时才会启动。因此,receiveDone.WaitOne() 事件永远不会被调用,因为 Receive 方法中的信号器永远不会被击中,因为 beginReceive 创建的工作线程在 StartClient 方法返回之前不会启动。

我还看到网上有很多关于这个问题的参考资料。

有人对这个问题有什么想法吗?

http://msdn.microsoft.com/en-us/library/fx6588te(v=vs.110).aspx

【问题讨论】:

我不明白这个问题,我以为这是在异步套接字中...为什么它被标记为ThreadPool 在调用 BeginReceive() 的方法完成后,它将开始长时间运行,long。网络连接比处理器慢得多。如果您使用调试器,甚至更长。这是异步套接字的重点,你永远不想等待它。如果回调永远不会运行,那么你做错了。可能是通过尝试连接到没有响应或被防火墙阻止的机器。 @Aron:我用 ThreadPool 标记了这个问题,因为我无法解释这种行为,就像 threadPool 用完了线程并且在线程可用之前不允许 beginReceive 启动。但这很可能不是这种情况,因为在进行此调用时只使用了大约 8 个线程。 @Hans:感谢您的评论,但这是针对我 14 年前编写的服务器,并且在生产中运行没有任何问题。唯一的问题是,在更新客户端应用程序时,我决定使用 .NET 异步方法并遇到了这个问题。具体问题是,在 MS 的这个示例中,beginRead 将不会调用 AsyncCallback,直到方法 startClient 返回,无论我等待多长时间,如果我在调试器中或其他情况下。这就像 AsyncCallback 线程加入了运行 startClient 方法的线程。 @KarlEasterly 这是否发生在来自 MSDN 的完全相同的代码中,都在单独的控制台应用程序中?另外,请注意该示例缺少对同步完成操作的检查 - 如果从 BeginXXX 返回的 IAsyncResult 设置了 CompletedSynchronously,则不会调用回调 - 您必须自己调用它。这在网络上不太可能发生,但如果您在 localhost 上进行测试,它可能会发生,尤其是在涉及所有等待的情况下。 【参考方案1】:

要获得完整的示例,您需要构建服务器和客户端,并同时运行它们。服务器示例中的Send 用于向客户端的请求发送响应

BeginSend(和其他 BeginXXX 方法)立即启动异步 I/O 请求 - 它不需要任何线程。一旦 I/O 请求完成(部分由 NIC 的物理硬件处理,部分由内核处理),回调将在线程池上的 I/O 线程上运行。因此,示例客户端是使用异步 I/O 编写的,但除此之外几乎是同步的。在这种情况下,sendDone.WaitOne(); 是完全没有必要的,但也无妨。

.NET 的异步套接字使用 IOCP,而不是多线程。基本思想是您有几个 I/O 线程,用于处理来自操作系统的异步 I/O“推送”。异步 I/O 本身不需要任何线程,来自线程池或其他,只需要一个用于回调的线程(在这种情况下,AcceptCallbackReadCallback 和类似方法)。

无论如何,这是一个过时的示例,与所有示例一样,您不应该开箱即用。它几乎是您可以构建的最简单的异步套接字服务器。事实上,这是更好的示例之一 - 它正确处理套接字关闭、正确读取数据,甚至处理原始消息帧(<EOF>)。

为了更详细地解释示例,有几件事同时发生。

服务器中的主线程只做一件事——重复启动异步 I/O“请求”以接受传入的 TCP 连接。等待句柄是为了避免不必要的轮询/循环 - 你可以看到它在执行 BeginAccept(异步 I/O 请求以接受连接)之前被重置,并在回调中设置(AcceptCallback) - 这将允许主线程再次循环并接受 连接。

AcceptCallback 也启动了一个异步 I/O 请求,这一次是为了接收数据。当网络接口接收到给定套接字的数据时,数据被保存到 DMA 缓冲区中,并且回调被发布到线程池 I/O 线程。这是ReadCallback 方法执行的地方。

因此,如您所见,如果没有请求,则除了主线程之外没有其他线程(这并不重要,因为退出主线程会终止应用程序)。只有当回调到来时,才会从 I/O 池中取出一个线程,并在回调完成后(几乎立即)返回。

【讨论】:

感谢您的 cmets。我了解你们关于 IOCP 的 cmets,但我不知道这一点。我也明白我不应该开箱即用,我只是在做提供的样本的初始原型以获得概念证明。我遇到的关键问题是 beginReceive 是一个异步方法调用。它应该调用与调用 beginReceive 的线程的状态无关的回调方法。正在发生的事情是,asyncCallback 的行为就好像它是一个连接到在 startClient 方法中运行的线程的线程。我很困惑……

以上是关于.NET 的异步套接字似乎与线程池有问题的主要内容,如果未能解决你的问题,请参考以下文章

27 Apr 18 GIL 多进程多线程使用场景 线程互斥锁与GIL对比 基于多线程实现并发的套接字通信 进程池与线程池 同步异步阻塞非阻塞

聊一聊线程池和Kotlin协程

Java中的线程池有啥用?

java所提供的线程池有几种之线程池总结

可缓存线程池newCachedThreadPool

常用的线程池有哪些?