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

标签 c# multithreading threadpool winsock asyncsocket

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

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 线程池)以及吞吐量非常稳定,我希望线程能够被重用。
  2. 线程数保持在较低水平,但我可以在进程资源管理器中看到线程被频繁创建和销毁。同样,我也不希望创建或销毁线程。

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

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

最佳答案

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

此问题与线程池线程注入(inject)有关,即线程的创建和销毁策略。定期注入(inject)线程池线程和销毁,以衡量新线程对线程池吞吐量的影响。

这称为线程探测。第 9 channel 的视频中对此有清楚的解释 CLR 4 - Inside the Thread Pool (跳转到 26:30)。

线程探测似乎总是用新创建的线程完成,而不是将线程移入和移出池。我想对于大多数应用程序来说,它可以更好地工作,因为它避免了使未使用的线程保持事件状态。

关于c# - 为什么 SocketAsyncEventArgs 的 Completed 回调经常在新创建的线程中执行,而不是使用有界线程池?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25269235/

相关文章:

java - Java中如何让ThreadPoolExecutor立即执行

c++ - std::async 不使用 std::launch::async 策略启动新线程

c# - 包括不在 LINQ 查询中工作但在 LINQ Fluent API 中工作

c# - 使用Unity拦截所有对IMyInterface.SomeMethod的调用

wpf - x 秒后删除图像源

JavaScript 和线程

c# - 什么时候使用内存映射文件?

c# - 将大量 XML 插入 sql azure 数据库的正确方法

c# - 为什么值类型和引用类型在任务循环中表现不同?

java - OpenJDK11 jstack输出解释