Почему обратный вызов Completed из SocketAsyncEventArgs часто выполняется во вновь созданных потоках вместо использования ограниченного пула потоков?

У меня есть простое клиентское приложение, которое получает байт-буферы из сети с низкой пропускной способностью. Вот код:

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);
}

Поведение в потоке приложения довольно любопытно:

  • Метод SocketAsyncEventsArgsCompleted часто запускается в новых потоках. Я бы ожидал, что через некоторое время новый поток не будет создан. Я бы ожидал, что потоки будут повторно использоваться из-за пула потоков (или пула потоков IOCP) и потому, что пропускная способность очень стабильная.
  • Количество потоков остается низким, но я вижу в проводнике процессов, что потоки часто создаются и уничтожаются. Аналогично, я бы не ожидал, что потоки будут созданы или уничтожены.

Можете ли вы объяснить поведение приложения?

Изменить: "Низкая" пропускная способность составляет 20 сообщений в секунду (примерно 200 КБ/с). Если я увеличиваю пропускную способность до более 1000 сообщений в секунду (50 Мбайт/с), поведение приложения не изменится.

Ответ 1

Низкая пропускная способность приложения не может объяснить создание и уничтожение потоков. Сокет получает 20 сообщений в секунду, что более чем достаточно, чтобы поддерживать поток в потоке (ожидающие потоки уничтожаются после простоя в течение 10 секунд).

Эта проблема связана с потоком потока thread injection, то есть стратегией создания и уничтожения потоков. Потоки пула потоков регулярно вводятся и уничтожаются, чтобы измерить влияние новых потоков на пропускную способность пула потоков.

Это называется исследование потоков. Это ясно объяснено в видео 9 канала CLR 4 - Внутри пула потоков (переход к 26:30).

Кажется, что пробоотвод потока всегда выполняется с вновь созданными потоками вместо перемещения потока в пул и из него. Я полагаю, что это работает лучше, чем это для большинства приложений, потому что это позволяет избежать неиспользуемого потока.

Ответ 2

Из MSDN

Начиная с .NET Framework 4, пул потоков создает и уничтожает рабочие потоки, чтобы оптимизировать пропускную способность, которая определяется как количество задач, выполняемых за единицу времени. Слишком мало потоки не могут оптимально использовать доступные ресурсы, тогда как слишком многие потоки могут увеличить конкуренцию ресурсов.

Примечание

Когда спрос низкий, фактическое количество потоков пулов потоков может опускаются ниже минимальных значений.

В основном это звучит так, будто ваша низкая пропускная способность приводит к тому, что пул потоков уничтожает потоки, поскольку они не требуются, и просто сидят, занимая ресурсы. Я бы не стал беспокоиться об этом. Поскольку MS явно заявляет:

В большинстве случаев пул потоков будет лучше работать со своими алгоритм распределения потоков.

Если вас действительно беспокоит, вы всегда можете опросить ThreadPool.GetAvailableThreads(), чтобы посмотреть пул, и посмотреть, как на него влияют разные сетевые потоки.