Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is the Completed callback from SocketAsyncEventArgs frequently executed in newly created threads instead of using a bounded thread pool?

I have a simple client application that receives byte buffers from the network with a low throughput. Here is the code:

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

The threading behavior of the application is quite curious:

  1. The SocketAsyncEventsArgsCompleted method is frequently run in new threads. I would have expected that after some time no new thread would be created. I would have expected the threads to be reused, because of the thread pool (or IOCP thread pool) and because the throughput is very stable.
  2. The number of threads stays low, but I can see in the process explorer that threads are frequently created and destroyed. Likewise, I would not have expected threads to be created or destroyed.

Can you explain the application behavior?

Edit: The "low" throughput is 20 messages per second (roughly 200 KB/s). If I increase the throughput to more than 1000 messages per second (50 MB/s), the application behavior does not change.

like image 230
cao Avatar asked Aug 12 '14 16:08

cao


2 Answers

The low application throughput itself cannot explain the thread creation and destruction. The socket receives 20 messages per seconds, which is more than enough to keep a thread alive (the waiting threads are being destroyed after spending 10 seconds idle).

This problem is related to the thread pool thread injection, i.e. the threads creation and destruction strategy. Thread pool threads are regularly injected and destroyed in order to measure the impact of new threads on the thread pool throughput.

This is called thread probing. It is clearly explained in the Channel 9 video CLR 4 - Inside the Thread Pool (jump to 26:30).

It seems like thread probing is always done with newly created threads instead of moving a thread in and out of the pool. I suppose it works better like this for most applications because it avoids to keep an unused thread alive.

like image 162
cao Avatar answered Nov 10 '22 13:11

cao


From MSDN

Beginning with the .NET Framework 4, the thread pool creates and destroys worker threads in order to optimize throughput, which is defined as the number of tasks that complete per unit of time. Too few threads might not make optimal use of available resources, whereas too many threads could increase resource contention.

Note

When demand is low, the actual number of thread pool threads can fall below the minimum values.

Basically it sounds like your low throughput is causing the thread pool to destroy threads since they are not required, and are just sat taking up resources. I wouldn't worry about it. As MS explicitly state:

In most cases the thread pool will perform better with its own algorithm for allocating threads.

If you're really bothered, you could always poll ThreadPool.GetAvailableThreads() to watch the pool, and see how different network throughputs affect it.

like image 30
GazTheDestroyer Avatar answered Nov 10 '22 13:11

GazTheDestroyer