为什么SocketAsyncEventArgs的Completed回调经常在新创建的线程中执行,而不是使用有界线程池

本文关键字:线程 执行 回调 Completed SocketAsyncEventArgs 创建 新创建 为什么 | 更新日期: 2023-09-27 18:17:52

我有一个简单的客户端应用程序,它以低吞吐量从网络接收字节缓冲区。下面是代码:

private static readonly HashSet<int> _capturedThreadIds = new HashSet<int>();
private static void RunClient(Socket socket)
{
    var e = new SocketAsyncEventArgs();
    e.SetBuffer(new byte[10000], 0, 10000);
    e.Completed += SocketAsyncEventsArgsCompleted;
    Receive(socket, e);
}
private static void Receive(Socket socket, SocketAsyncEventArgs e)
{
    var isAsynchronous = socket.ReceiveAsync(e);
    if (!isAsynchronous)
        SocketAsyncEventsArgsCompleted(socket, e);
}
private static void SocketAsyncEventsArgsCompleted(object sender, SocketAsyncEventArgs e)
{
    if (e.LastOperation != SocketAsyncOperation.Receive || e.SocketError != SocketError.Success || e.BytesTransferred <= 0)
    {
        Console.WriteLine("Operation: {0}, Error: {1}, BytesTransferred: {2}", e.LastOperation, e.SocketError, e.BytesTransferred);
        return;
    }
    var thread = Thread.CurrentThread;
    if (_capturedThreadIds.Add(thread.ManagedThreadId))
        Console.WriteLine("New thread, ManagedId: " + thread.ManagedThreadId + ", NativeId: " + GetCurrentThreadId());
    //Console.WriteLine(e.BytesTransferred);
    Receive((Socket)sender, e);
}

应用程序的线程行为很奇怪:

  1. SocketAsyncEventsArgsCompleted方法经常在新线程中运行。我本以为一段时间后不会创建新的线程。我本来希望线程被重用,因为线程池(或IOCP线程池),因为吞吐量是非常稳定的。线程的数量仍然很低,但我可以在进程资源管理器中看到线程经常被创建和销毁。同样,我也不希望线程被创建或销毁。

你能解释应用程序的行为吗?

编辑:"低"吞吐量是每秒20条消息(大约200 KB/s)。如果我将吞吐量提高到每秒1000条消息以上(50 MB/s),应用程序行为不会改变。

为什么SocketAsyncEventArgs的Completed回调经常在新创建的线程中执行,而不是使用有界线程池

低应用程序吞吐量本身不能解释线程的创建和销毁。套接字每秒接收20条消息,这足以使线程保持活动状态(等待的线程在空闲10秒后将被销毁)。

这个问题与线程池线程注入有关,即线程的创建和销毁策略。为了测量新线程对线程池吞吐量的影响,定期注入和销毁线程池线程。

这被称为线程探测。在第9频道视频CLR 4 -线程池内部(跳到26:30)中有清楚的解释。

似乎线程探测总是用新创建的线程完成,而不是将线程移进移出池。我认为这样对大多数应用程序来说效果更好,因为它避免了保持未使用的线程存活。

From MSDN

从。net Framework 4开始,线程池创建和销毁工作线程以优化吞吐量,即定义为单位时间内完成的任务数量。太少了线程可能无法最优地利用可用资源,而线程也是如此过多的线程会增加资源争用。

注意

当需求较低时,线程池的实际线程数可以低于最小值

基本上,这听起来像是你的低吞吐量导致线程池销毁线程,因为它们不是必需的,只是占用资源。我不会担心的。正如MS明确声明的:

在大多数情况下,线程池使用自己的线程池会执行得更好分配线程的算法。

如果您真的很烦恼,您可以始终轮询ThreadPool.GetAvailableThreads()来监视池,并查看不同的网络吞吐量如何影响它。