I’ve recently talked to several experienced developers that struggle with the async/await keywords introduced in .NET 4.5. There are literally hundred of good articles out there on the topic; however, they continue to struggle with two misconceptions. The first is that the async keyword is just another way to do multithreading. The other is that multithreading and asynchrony are meant to improve performance. This blog post will give some history and dispel these fallacies.
Misconception 1: Multithreading improves performance
I may be dating myself here, but I remember working with 16 bit windows (when I was a young humpback freak, before obtaining hideous status). Back then, the OS would hand over the CPU to an application. The application completely controlled the CPU and would relinquish control once it ran to completion. The OS had no way of breaking into the application process. Therefore, fatal exceptions or infinite loops would cause the machine to become unresponsive and the only way to regain control was a reboot. In order to combat this problem, threads were introduced.
In a nutshell, a thread is a virtualisation of the CPU. Threads are provisioned by the OS and scheduled to run for a quantum (slice of time). At the end of each quantum, the OS performs a context switch. A context switch consists of storing away the current state of the registers and restoring the registers for the next thread. Threads are scheduled by the OS in a round robin fashion. This is how OS can do more than one thing at a time. If an application gets caught in an infinite loop, the OS can kill it from a different thread.
The main take away concept here is that, although very efficient, new threads and context switches are not free. The more threads there are, the more context switches, and the less time each thread has on the CPU. When you introduce threading, the overall system performance goes DOWN. Threading was introduced to make the OS fault tolerant against application errors. Any time you can perform work in a single thread, that is preferred.
The most common use of multithreading is to keep an UI responsive. A UI thread needs to be available to respond to user input; therefore, work needs to be done in different threads.
Now, it’s time to make a seemingly contradictory statement: multithreading can make your programs run faster. Raw performance and overall application processing time are two different things. Today, many machines come equipped with multiple CPUs. If you have CPU bound work (see the next section), you can reduce application processing time by scheduling multiple CPUs to do work at the same time. This assumes that the OS does not have the CPUs busy doing other things…
Misconception 2: Asynchronous means Multithreading FALSE
Although the two concepts are somewhat related (you can do multithreading with the async/await keywords), they are very different. Asynchronous calls do pretty much the opposite of multithreading; they make a single thread do more. This is accomplished by not holding a thread hostage waiting on I/O bound work. Before we continue, lets define I/O bound verses CPU bound work.
- I/O bound work
- The OS makes a request to a device driver (network card, printer, hard drive, etc…). The device performs some work and returns data back to the OS.
- CPU bound work
- Actual processor cycles (long loops, calculations, etc…)
In reality, there is very little CPU bound work. Yes, it does exist but the majority of time intensive tasks fall into the I/O bound category.
When your application makes a request to a device driver (via the OS), it does not return data immediately. The OS sends the request to the device and then forgets about it until the device sends an interrupt indicating that it’s done. If your application is not using an asynchronous method call, the thread will just hang doing nothing while the device is doing it’s thing. An asynchronous method call will return the thread back to the OS so it can do other work. Once the OS receives the “I’m done” interrupt, the OS will schedule the work to resume. In this way, the OS avoids the overhead of creating new threads.
The above is an greatly simplified version of things; however, I hope it gave you enough of a conceptual understanding to enable you to use async/await in the right way. There are plenty of computer science resources out there if you want a deeper dive…
Thank you for reading! As always, feel free to hit me up with any questions.