I have been known to complain about other people's explanation of async/await in C# (for example, .NET Threading Gotchas and C# Async Await, Simply). I have even been told that if I know so much why don't I write a post instead of taking cheap shots from the sidelines - which is fair enough really. The problem is that the subject can get very complicated if you want to understand it deeply.
So here is my attempt to explain it. I will explain what I consider the core concepts in 2 levels of difficulty: simply, and more complicated. I should also warn you the journey is long ...
So I will break it up as much as possible. The first part will try to explain what asynchronous programming is.
What is asynchronous programming?
Simply: What is asynchronous programming?
Programming that allows code to be executed outside of the main program flow. The code below shows where the main program flow starts and ends.
public static class Example
{
public static void Main()
{
// Here starts the main program flow.
Console.WriteLine("Do this first");
Console.WriteLine("Do this second");
// The main program flow ends once the main returns.
}
}
So any code executed outside this program flow is said to be executed asynchronously.
More complicated: What is asynchronous programming?
public static void Main()
{
// Here starts the main program flow.
Thread worker = new Thread(() =>
{
// Sleep the worker thread for 100 milliseconds.
Thread.Sleep(100);
Console.WriteLine("Worker Thread: Woke up");
});
// Start a worker thread and execute the work
// asynchronously.
worker.Start();
Thread.Sleep(100);
Console.WriteLine("Main Thread: Woke up");
// Block the main thread until the worker completes.
worker.Join();
Console.WriteLine("Main Thread: Do this last");
// The main program flow ends once the main returns.
}
So I have just slammed you with the concept of a thread and not explained it. You can think of a thread in C# as a light-weight program that can share memory with other threads including the main thread within the same application.
The Main Thread associated with the program above starts a Worker Thread, goes to sleep for 100 milliseconds, then waits for the Worker Thread to complete. (Note that a Worker Thread in this case is just another thread that is not the Main Thread). The Worker Thread sleeps for 100 milliseconds, then completes.
The output of the application is shown below ...
Main Thread: Woke up
Worker Thread: Woke up
Main Thread: Do this last
Did you expect the Main Thread to output "Woke up" before the Worker Thread?
If so, congratulations ... maybe. If not well welcome to the joy of asynchronous programming. If you were unsure then I agree. I think there is no explicit guarantee that the Main Thread will output before the Worker Thread. It probably depends on how Console.WriteLine is implemented and how the threads are scheduled and prioritised by the runtime!
If you are interested in when the Worker Thread might output before the Main Thread then consider the following code otherwise you can skip it ...
public static void Main()
{
int workerRanFirstCount = 0;
int mainRanFirstCount = 0;
int totalRuns = 10;
var runOrder = new ConcurrentQueue<string>();
var workers = new List<Thread>();
for (int ii = 0; ii < totalRuns; ++ii)
{
workers.Add(new Thread(() =>
{
Thread.Sleep(100);
runOrder.Enqueue("worker");
}));
}
foreach (var worker in workers)
{
worker.Start();
Thread.Sleep(100);
runOrder.Enqueue("main");
// Block the main thread until the worker completes.
worker.Join();
if (runOrder.First() == "worker") ++workerRanFirstCount;
if (runOrder.First() == "main") ++mainRanFirstCount;
runOrder.Clear();
}
Console.WriteLine($"Main ran first count = {mainRanFirstCount}; Worker ran first = {workerRanFirstCount}");
}
The output from a number of runs on my machine is shown below ...
Main ran first count = 10; Worker ran first = 0
Main ran first count = 8; Worker ran first = 2
Main ran first count = 6; Worker ran first = 4
In conclusion the exact interleaving of threaded operations can be difficult to determine in asynchronous programming. Why am I telling you this? Because to fundamentally understand and implement predictable asynchronous code, you need to understand this issue.
I will try to write up a follow on post at some point but I am interested in what you think of my explanation of asynchronous programming. Is it too complicated, or just right so far?