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

cao*_*cao 10 c# multithreading winsock asyncsocket threadpool

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

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);
}
Run Code Online (Sandbox Code Playgroud)

应用程序的线程行为非常好奇:

  1. SocketAsyncEventsArgsCompleted方法经常在新线程中运行.我原本预计在一段时间后不会创建新的线程.我原本期望线程被重用,因为线程池(或IOCP线程池)并且因为吞吐量非常稳定.
  2. 线程数保持较低,但我可以在进程资源管理器中看到线程经常被创建和销毁.同样,我不希望创建或销毁线程.

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

编辑:"低"吞吐量是每秒20条消息(大约200 KB/s).如果我将吞吐量增加到每秒超过1000条消息(50 MB/s),则应用程序行为不会更改.

cao*_*cao 6

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

此问题与线程池线程注入有关,即线程创建和销毁策略.定期注入和销毁线程池线程,以便测量新线程对线程池吞吐量的影响.

这称为线程探测.在第9频道视频CLR 4 - 线程池内(跳至26:30)中清楚地解释了这一点.

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