Search code examples
c#multithreadingasynchronousasync-awaitthread-sleep

How does asynchronous programming work with threads when using Thread.Sleep()?


Presumptions/Prelude:

  1. In previous questions, we note that Thread.Sleep blocks threads see: When to use Task.Delay, when to use Thread.Sleep?.
  2. We also note that console apps have three threads: The main thread, the GC thread & the finalizer thread IIRC. All other threads are debugger threads.
  3. We know that async does not spin up new threads, and it instead runs on the synchronization context, "uses time on the thread only when the method is active". https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/task-asynchronous-programming-model

Setup:
In a sample console app, we can see that neither the sibling nor the parent code are affected by a call to Thread.Sleep, at least until the await is called (unknown if further).

var sw = new Stopwatch();
sw.Start();
Console.WriteLine($"{sw.Elapsed}");
var asyncTests = new AsyncTests();

var go1 = asyncTests.WriteWithSleep();
var go2 = asyncTests.WriteWithoutSleep();

await go1;
await go2;
sw.Stop();
Console.WriteLine($"{sw.Elapsed}");
        
Stopwatch sw1 = new Stopwatch();
public async Task WriteWithSleep()
{
    sw1.Start();
    await Task.Delay(1000);
    Console.WriteLine("Delayed 1 seconds");
    Console.WriteLine($"{sw1.Elapsed}");
    Thread.Sleep(9000);
    Console.WriteLine("Delayed 10 seconds");
    Console.WriteLine($"{sw1.Elapsed}");
    sw1.Stop();
}
public async Task WriteWithoutSleep()
{
    await Task.Delay(3000);
    Console.WriteLine("Delayed 3 second.");
    Console.WriteLine($"{sw1.Elapsed}");
    await Task.Delay(6000);
    Console.WriteLine("Delayed 9 seconds.");
    Console.WriteLine($"{sw1.Elapsed}");
}

Question: If the thread is blocked from execution during Thread.Sleep, how is it that it continues to process the parent and sibling? Some answer that it is background threads, but I see no evidence of multithreading background threads. What am I missing?


Solution

  • I see no evidence of multithreading background threads. What am I missing?

    Possibly you are looking in the wrong place, or using the wrong tools. There's a handy property that might be of use to you, in the form of Thread.CurrentThread.ManagedThreadId. According to the docs,

    A thread's ManagedThreadId property value serves to uniquely identify that thread within its process.

    The value of the ManagedThreadId property does not vary over time

    This means that all code running on the same thread will always see the same ManagedThreadId value. If you sprinkle some extra WriteLines into your code, you'll be able to see that your tasks may run on several different threads during their lifetimes. It is even entirely possible for some async applications to have all their tasks run on the same thread, though you probably won't see that behaviour in your code under normal circumstances.

    Here's some example output from my machine, not guaranteed to be the same on yours, nor is it necessarily going to be the same output on successive runs of the same application.

    00:00:00.0000030
     * WriteWithSleep on thread 1 before await
     * WriteWithoutSleep on thread 1 before first await
     * WriteWithSleep on thread 4 after await
    Delayed 1 seconds
    00:00:01.0203244
     * WriteWithoutSleep on thread 5 after first await
    Delayed 3 second.
    00:00:03.0310891
     * WriteWithoutSleep on thread 6 after second await
    Delayed 9 seconds.
    00:00:09.0609263
    Delayed 10 seconds
    00:00:10.0257838
    00:00:10.0898976
    

    The business of running tasks on threads is handled by a TaskScheduler. You could write one that forces code to be single threaded, but that's not often a useful thing to do. The default scheduler uses a threadpool, and as such tasks can be run on a number of different threads.