0

According to

If async-await doesn't create any additional threads, then how does it make applications responsive?

a C# task, executed by await ... doesn't create a separate thread for the target Task. However, I observed, that such a task is executed not every time from the same thread, but can switch it's thread.

I still do not understand, what's going on.

public class TestProgram
{
    private static async Task HandleClient(TcpClient clt)
    {
        using NetworkStream ns = clt.GetStream();
        using StreamReader sr = new StreamReader(ns);
        while (true)
        {
            string msg = await sr.ReadLineAsync();
            Console.WriteLine($"Received in {System.Threading.Thread.CurrentThread.ManagedThreadId} :({msg.Length} bytes):\n{msg}");
        }
    }
    private static async Task AcceptConnections(int port)
    {
        TcpListener listener = new TcpListener(IPAddress.Parse("127.0.0.1"), port);
        listener.Start();
        while(true)
        {
            var client = await listener.AcceptTcpClientAsync().ConfigureAwait(false);
            Console.WriteLine($"Accepted connection for port {port}");
            var task = HandleClient(client);
        }
    }

    public async static Task Main(string[] args)
    {

        var task1=AcceptConnections(5000);
        var task2=AcceptConnections(5001);

        await Task.WhenAll(task1, task2).ConfigureAwait(false);
    }
}

This example code creates two listeners for ports 5000 and 5001. Each of it can accept multiple connections and read independently from the socket created.

Maybe it is not "nice", but it works and I observed, that messages received from different sockets are sometimes handled in the same thread, and that the used thread for execution even changes.

Accepted connection for port 5000
Accepted connection for port 5000
Accepted connection for port 5001
Received new message in 5 :(17 bytes):
Port-5000 Message from socket-1
Received new message in 7 :(18 bytes):
Port-5000 Message  from socket-1
Received new message in 7 :(18 bytes):
Port-5000 Message  from socket-1
Received new message in 7 :(20 bytes):
Port-5000 Message  from socket-2
Received new message in 7 :(18 bytes):
Port-5000 Message  from socket-2
Received new message in 7 :(18 bytes):
Port-5001 Message  from socket-3
Received new message in 8 :(17 bytes):
Port-5001 Message  from socket-3

(texts manually edit for clarity, byte lengths are not valid)

If there is heavy load (I didn't test it yet), how many threads would be involved in order to execute those parallel tasks? I heard about a thread pool, but do not know, how to have some influence on it.

Or is it totally wrong asking that and I do not at all have to care about what particular thread is used and how many of them are involved?

MichaelW
  • 1,328
  • 1
  • 15
  • 32

1 Answers1

2

a C# task, executed by await ... doesn't create a separate thread for the target Task.

One important correction: a task is not "executed" by await. Asynchronous tasks are already in-progress by the time they're returned. await is used by the consuming code to perform an "asynchronous wait"; i.e., pause the current method and resume it when that task has completed.

I observed, that such a task is executed not every time from the same thread, but can switch it's thread.

I observed, that messages received from different sockets are sometimes handled in the same thread, and that the used thread for execution even changes.

The task isn't "executed" anywhere. But the code in the async method does have to run, and it has to run on a thread. await captures a "context" when it pauses the method, and when the task completes it uses that context to resume executing the method. Console apps don't have a context, so the method resumes on any available thread pool thread.

If there is heavy load (I didn't test it yet), how many threads would be involved in order to execute those parallel tasks? I heard about a thread pool, but do not know, how to have some influence on it.

Or is it totally wrong asking that and I do not at all have to care about what particular thread is used and how many of them are involved?

You usually do not have to know; as long as your code isn't blocking thread pool threads you're generally fine. It's important to note that zero threads are being used while doing I/O, e.g., while listening/accepting a new TCP socket. There's no thread being blocked there. Thread pool threads are only borrowed when they're needed.

For the most part, you don't have to worry about it. But if you need to, the thread pool has several knobs for tweaking.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810