- When executing I/O operations (Disk/Network) on the primary application thread, Windows notices that your thread is waiting for the I/O operation to complete.
- Because of this, Windows pauses the current thread so that it doesn’t use any CPU resources.
- But while doing this, it still uses memory, and the thread can’t be used to serve other requests, which in turn will lead to new threads being created if requests come in.
- Asynchrony is valuable for applications that access the UI thread because all UI-related activity usually shares one thread.
- If any process is blocked in a synchronous application, all are blocked.
- Your application stops responding, and you might conclude that it has failed when instead it's just waiting.
- Instead of blocking the thread until the I/O operation finishes,
async
code solves this by returning a Task
object that represents the result of the asynchronous operation.
- By setting a continuation on the
Task
, you can continue when the I/O is done.
- In the meantime, the thread is available for other work.
- When the I/O operation finishes, Windows notifies the runtime, and the continuation Task is scheduled on the thread pool.
- C# has two keywords to simplify writing asynchronous code:
async
and await
.
- Use the
async
to mark a method for asynchronous operations.
- The compiler responds to this by transforming your code into a state machine.
- A method marked with
async
just starts running synchronously on the current thread.
- What it does is enable the method to be split into multiple pieces.
- The boundaries of these pieces are marked with the
await
keyword.
- The
await
keyword allows to write a code that looks synchronous but behaves in an asynchronous way.
- When you use the
await
keyword, the compiler generates code that will see whether the asynchronous operation is already finished.
- If it is, the caller method just continues running synchronously.
- If it’s not yet completed, the state machine will hook up a continuation method that should run when the
Task
completes.
- The method yields control to the calling thread, and the created thread can be used for other works.
public static async Task<string> DownloadContent()
{
using (var client = new HttpClient())
{
return await client.GetStringAsync("<http://www.microsoft.com>");
}
}
CPU-Bound Tasks
- Doing a CPU-bound task is different from an I/O-bound task.
- CPU-bound tasks always use some thread to execute their work.
- An asynchronous I/O-bound task doesn’t use a thread until the I/O is finished.
- With client applications that needs to stay responsive while background operations are running, use the
await
keyword to offload the operation to another thread.
- Although this does not improve performance, it does improve responsiveness.
- The
await
keyword also makes sure that the remainder of your method runs on the correct user interface thread so you can update the user interface.
- Just wrapping each and every operation in a task and awaiting them won’t make applications perform any better.
- It could, however, improve responsiveness, which is very important in client applications.
- For example, the
FileStream
class exposes WriteAsync
and ReadAsync
.
- They use an implementation that makes use of actual asynchronous I/O.
- This way, they don’t use a thread while they are waiting on the hard drive of your system to read or write some data.
- When an exception happens in an asynchronous method, you normally expect an
AggregateException
.
- However, the generated code helps you unwrap the
AggregateException
and throws the first of its inner exceptions.
- This makes the code more intuitive to use and easier to debug.