If you want to make web requests in C#, or just want to do some background processing, you’ll need to use asynchronous background tasks to not block up the main thread. We’ll discuss what they are, and how to use them.

What Is Async/Await?

To use Tasks, you must first understand the concept of async/await. C# tasks don’t have to run asynchronously, but considering the sole purpose of them is to represent an asynchronous operation, they almost always will run async. You don’t want to run operations like fetching web requests and writing to hard drives on the main thread, because it would hold up the rest of the application (including the UI) while waiting for the result.

async/await is special syntax used to deal with asynchronous operations. If a function is marked as async, it will usually return a Task, except in cases of event handlers that return void.

Inside the async function, you can use the await keyword to wait for async operations to finish without blocking the whole thread. Everything that comes after the await keyword will only run after the awaited operation finishes.

The value being awaited must be a Task, as the two go hand in hand with each other. When you call the SendRequest() function, it returns a Task, and the program waits until that task finishes. You can think of await as a keyword used to return or wait for the value of a task to finish.

What Are Tasks?

Tasks are wrappers used to deal with asynchronous functions. They essentially represent a value that will be returned in the future. You can use the await keyword to wait for the result, or access it directly by checking if Task.IsCompleted and then reading the value of Task.Result.

You can create them by writing an async function with a return type of Task. Then, all you have to do is return a value of type T, and .NET will interpret that as returning a Task. You can use await inside this task to wait for async operations, which in turn return a task themselves.

You can start running a Task using Task.Run(Action action). This will queue up the Task on the thread pool, which will run in the background on a different thread. The thread pool takes a queue of tasks, and assigns them to CPU threads for processing. Once they return, they’re put into the list of completed tasks where their values can be accessed.

However, even though it’s on a background thread, it’s still very important to use async/await. If you make a blocking call to an API on a background thread, and don’t await it, .NET will keep that thread blocked until it completes, filling up the thread pool with useless threads doing nothing but hurting performance.

If you need to await a task from the UI thread, start it with Task.Run, then check regularly to see if the task has been completed. If it has, you can handle the value.

You can also run and await tasks inside other Tasks. For example, say you have a function inside a task, DoExpensiveCalculation(), that takes a while to execute. Rather than processing it synchronously, you can write it as a Task, and queue up a background thread at the beginning of the main task. Then, when you need the value from that calculation, you can simply await the task, and it will yield until the task is completed, and the return value is returned.