📓 3.5.0.2 Async and Await
During Intermediate JavaScript, we learned how to write asynchronous code and manage asynchronicity while performing complex actions like API calls. In this lesson, we'll discuss how C# handles asynchronous code.
Why is this relevant now? In the next few lessons, we'll learn how to authenticate users with Identity. This will require our applications to manage asynchronous actions, so we need to learn how to recognize and write asynchronous methods.
Synchronous Operations
So far the C# code we've written is synchronous and our code executes a single line at a time. The Edit()
action in the ItemsController.cs
is an example of a synchronous method:
...
public ActionResult Edit(int id)
{
Item thisItem = _db.Items.FirstOrDefault(item => item.ItemId == id);
ViewBag.CategoryId = new SelectList(_db.Categories, "CategoryId", "Name");
return View(thisItem);
}
...
Let's walk through what occurs when this synchronous method is called:
When
Edit()
is called, our program looks in the database to find a specific item.After locating the item, our program returns the Edit
View()
.While our program is looking in the database, the browser waits. It cannot return the view until
thisItem
is found because the line of code attempting to locatethisItem
appears before we return the view.When
thisItem
is located, the application can return the view to be displayed in the browser.
Because the lines of code are executed in order, this is a synchronous operation.
Asynchronous Operations
On the other hand, an asynchronous (also, "async") operation allows other code to run while a method is waiting to return. This is very similar to what we learned in JavaScript. However, the code we write to manage this process looks much different.
There are three primary parts to an async C# method:
The
async
keywordThe
await
keywordA special
Task
class that represents an action or actions the program may not have completed yet because they're async.
Let's walk through an example async method:
static async void ProcessTextFileAsync()
{
Task<int> asyncTask = ExampleAsyncMethodThatTakesAWhile();
Console.WriteLine("Please wait patiently while I run the ExampleAsyncMethodThatTakesAWhile().");
int x = await asyncTask;
Console.WriteLine("Return value of ExampleAsyncMethodThatTakesAWhile(): " + x);
}
First, the
async
keyword is included in the method's signature:static async void ProcessTextFileAsync()
. Async methods should always have anasync
modifier in their declaration. It tells our application the method should run asynchronously.Next, notice the
await
keyword in the lineint x = await asyncTask
. As its name suggests, this keyword makes the application wait until the specified async actionasyncTask
is completed. OnceasyncTask
is completed, it will definex
. This gives us a great deal of control over our code. We can allow multiple lines of code to run asynchronously and then add multiple "waiting points" withawait
.Our hypothetical async method
ExampleAsyncMethodThatTakesAWhile()
stores the return data in the variableasyncTask
. Notice thatasyncTask
has the type ofTask<int>
. In C#, an asynchronous method can only returnvoid
or aTask
object representing the asynchronous operation itself. ATask
represents ongoing work. While aTask
isvoid
by default, we can specify what type the task will eventually return by using theTask<TResult>
class. For example:
Task<int>
returns anint
;Task<string>
returns astring
;Task<ActionResult>
returns anActionResult
.
- In the code above we want the result to be the integer
x
. In order to turn aTask<int>
into anint
, we use theawait
keyword. This forces the program to wait untiltask
is appropriately defined as anint
before moving on to subsequent lines of code.
Let's consider one more pseudocode example:
static async void DoMyHolidayErrandsForMeAsync()
{
Task<int> determineHowManyGiftsIShouldBuy = ProcessSantasList("C:\\naughty_or_nice.txt");
BakeSugarCookies();
HangLights();
int giftNumber = await determineHowManyGiftsIShouldBuy;
for (int index = 0; index < giftNumber; index++)
{
BuyHolidayGifts();
MarkOffList(index);
}
}
Here, we have an async
method called DoMyHolidayErrandsForMeAsync()
. We could describe the method as doing the following: "I need you to do holiday errands. Start determining how many gifts we need to buy and feel free to multi-task and bake the cookies and hang lights while you're doing that, too. But wait to define giftNumber
until after the ProcessSantasList()
method fully finishes because we need its results before continuing. After we have that number, we can buy the necessary number of gifts and start marking them off our list."
Rules and Conventions
Before we wrap up, let's review the rules and conventions for async methods in C#:
Async methods always have an
async
modifier in their declaration, as seen above.It's also a best practice to include the term
Async
at the end of the method name as seen in theProcessTextFileAsync()
andDoMyHolidayErrandsForMeAsync()
methods.Again, an asynchronous method can only return
void
or aTask
object representing the asynchronous operation itself.We can specify what type the
Task
will eventually become. For example, the typeTask<int>
will resolve to anint
,Task<string>
returns astring
, andTask<ActionResult>
returns anActionResult
. When we do this, we're using theTask<TResult>
class.The
await
keyword can only be used in a method that includes theasync
modifier in its signature.Because
await
waits for aTask
to finish and return its specified data type, we can use it to "unwrap" aTask
. For instance, if weawait
something declared as aTask<int>
, we'll receive anint
value, because that's what thatTask
returns when its async code is finished running. Similarly, if we useawait
on aTask<string>
, we'd receive a string value.
Namespace
The Task
and Task<TResult>
classes belong to the System.Threading.Tasks
namespace. So anytime we use a Task
in our code, we'll need to add the following using directive.
using System.Threading.Tasks;
The System.Threading.Tasks
namespace contains a lot of classes to help us manage concurrent and asynchronous code. To learn more, visit the MS Docs for the System.Threading.Tasks
namespace.
Additional Resources
To review the official guide on asynchrony with C#, visit the MS docs on async and await.
For some examples of async/await methods in use, check out this article from dotnetperls.
For more information about the
Task<TResult>
class, we recommend the MS Documentation.