Asynchronous Programming Scenarios - C# _ Microsoft Learn
Asynchronous Programming Scenarios - C# _ Microsoft Learn
Article • 10/12/2023
If you have any I/O-bound needs (such as requesting data from a network, accessing a database, or
reading and writing to a file system), you'll want to utilize asynchronous programming. You could also
have CPU-bound code, such as performing an expensive calculation, which is also a good scenario for
writing async code.
C# has a language-level asynchronous programming model, which allows for easily writing
asynchronous code without having to juggle callbacks or conform to a library that supports
asynchrony. It follows what is known as the Task-based Asynchronous Pattern (TAP).
For I/O-bound code, you await an operation that returns a Task or Task<T> inside of an async
method.
For CPU-bound code, you await an operation that is started on a background thread with the
Task.Run method.
The await keyword is where the magic happens. It yields control to the caller of the method that
performed await , and it ultimately allows a UI to be responsive or a service to be elastic. While there
are ways to approach async code other than async and await , this article focuses on the language-
level constructs.
7 Note
C#
The code expresses the intent (downloading data asynchronously) without getting bogged down in
interacting with Task objects.
The best way to handle this is to start a background thread, which does the work using Task.Run , and
await its result using await . This allows the UI to feel smooth as the work is being done.
C#
This code clearly expresses the intent of the button's click event, it doesn't require managing a
background thread manually, and it does so in a non-blocking way.
For the theoretically inclined, this is an implementation of the Promise Model of asynchrony .
Here are two questions you should ask before you write any code:
1. Will your code be "waiting" for something, such as data from a database?
If the work you have is I/O-bound, use async and await without Task.Run . You should not use the
Task Parallel Library.
If the work you have is CPU-bound and you care about responsiveness, use async and await , but
spawn off the work on another thread with Task.Run . If the work is appropriate for concurrency and
parallelism, also consider using the Task Parallel Library.
Additionally, you should always measure the execution of your code. For example, you may find
yourself in a situation where your CPU-bound work is not costly enough compared with the overhead
of context switches when multithreading. Every choice has its tradeoff, and you should pick the
correct tradeoff for your situation.
More examples
The following examples demonstrate various ways you can write async code in C#. They cover a few
different scenarios you may come across.
7 Note
If you plan on doing HTML parsing in production code, don't use regular expressions. Use a
parsing library instead.
C#
[HttpGet, Route("DotNetCount")]
static public async Task<int> GetDotNetCount(string URL)
{
// Suspends GetDotNetCount() to allow the caller (the web server)
// to accept another request, rather than blocking on this one.
var html = await s_httpClient.GetStringAsync(URL);
return Regex.Matches(html, @"\.NET").Count;
}
Here's the same scenario written for a Universal Windows App, which performs the same task when a
Button is pressed:
C#
// Any other work on the UI thread can be done here, such as enabling a Progress
Bar.
// This is important to do here, before the "await" call, so that the user
// sees the progress bar before execution of this method is yielded.
NetworkProgressBar.IsEnabled = true;
NetworkProgressBar.Visibility = Visibility.Visible;
NetworkProgressBar.IsEnabled = false;
NetworkProgressBar.Visibility = Visibility.Collapsed;
}
This example shows how you might grab User data for a set of userId s.
C#
C#
Although it's less code, use caution when mixing LINQ with asynchronous code. Because LINQ uses
deferred (lazy) execution, async calls won't happen immediately as they do in a foreach loop unless
you force the generated sequence to iterate with a call to .ToList() or .ToArray() . The above
example uses Enumerable.ToArray to perform the query eagerly and store the results in an array. That
forces the code id => GetUserAsync(id) to run and start the task.
async methods need to have an await keyword in their body or they will never yield!
This is important to keep in mind. If await is not used in the body of an async method, the C#
compiler generates a warning, but the code compiles and runs as if it were a normal method.
This is incredibly inefficient, as the state machine generated by the C# compiler for the async
method is not accomplishing anything.
Add "Async" as the suffix of every async method name you write.
This is the convention used in .NET to more easily differentiate synchronous and asynchronous
methods. Certain methods that aren't explicitly called by your code (such as event handlers or
web controller methods) don't necessarily apply. Because they are not explicitly called by your
code, being explicit about their naming isn't as important.
async void is the only way to allow asynchronous event handlers to work because events do not
have return types (thus cannot make use of Task and Task<T> ). Any other use of async void
does not follow the TAP model and can be challenging to use, such as:
Exceptions thrown in an async void method can't be caught outside of that method.
async void methods are difficult to test.
async void methods can cause bad side effects if the caller isn't expecting them to be async.
Lambda expressions in LINQ use deferred execution, meaning code could end up executing at a
time when you're not expecting it to. The introduction of blocking tasks into this can easily result
in a deadlock if not written correctly. Additionally, the nesting of asynchronous code like this can
also make it more difficult to reason about the execution of the code. Async and LINQ are
powerful but should be used together as carefully and clearly as possible.
Blocking the current thread as a means to wait for a Task to complete can result in deadlocks
and blocked context threads and can require more complex error-handling. The following table
provides guidance on how to deal with waiting for tasks in a non-blocking way:
ノ Expand table
A common question is, "when should I use the Task.ConfigureAwait(Boolean) method?". The
method allows for a Task instance to configure its awaiter. This is an important consideration
and setting it incorrectly could potentially have performance implications and even deadlocks.
For more information on ConfigureAwait , see the ConfigureAwait FAQ .
Don't depend on the state of global objects or the execution of certain methods. Instead,
depend only on the return values of methods. Why?
Code will be easier to reason about.
Code will be easier to test.
Mixing async and synchronous code is far simpler.
Race conditions can typically be avoided altogether.
Depending on return values makes coordinating async code simple.
(Bonus) it works really well with dependency injection.
Complete example
The following code is the complete text of the Program.cs file for the example.
C#
using System.Text.RegularExpressions;
using System.Windows;
using Microsoft.AspNetCore.Mvc;
class Button
{
public Func<object, object, Task>? Clicked
{
get;
internal set;
}
}
class DamageResult
{
public int Damage
{
get { return 0; }
}
}
class User
{
public bool isEnabled
{
get;
set;
}
public int id
{
get;
set;
}
}
// <GetUsersForDataset>
private static async Task<User> GetUserAsync(int userId)
{
// Code omitted:
//
// Given a user Id {userId}, retrieves a User object corresponding
// to the entry in the database with {userId} as its Id.
// <GetUsersForDatasetByLINQ>
private static async Task<User[]> GetUsersAsyncByLINQ(IEnumerable<int> userIds)
{
var getUserTasks = userIds.Select(id => GetUserAsync(id)).ToArray();
return await Task.WhenAll(getUserTasks);
}
// </GetUsersForDatasetByLINQ>
// <ExtractDataFromNetwork>
[HttpGet, Route("DotNetCount")]
static public async Task<int> GetDotNetCount(string URL)
{
// Suspends GetDotNetCount() to allow the caller (the web server)
// to accept another request, rather than blocking on this one.
var html = await s_httpClient.GetStringAsync(URL);
return Regex.Matches(html, @"\.NET").Count;
}
// </ExtractDataFromNetwork>
Console.WriteLine("Application ending.");
}
}
// Example output:
//
// Application started.
// Counting '.NET' phrase in websites...
// https://learn.microsoft.com: 0
// https://learn.microsoft.com/aspnet/core: 57
// https://learn.microsoft.com/azure: 1
// https://learn.microsoft.com/azure/devops: 2
// https://learn.microsoft.com/dotnet: 83
// https://learn.microsoft.com/dotnet/desktop/wpf/get-started/create-app-visual-studio:
31
// https://learn.microsoft.com/education: 0
// https://learn.microsoft.com/shows/net-core-101/what-is-net: 42
// https://learn.microsoft.com/enterprise-mobility-security: 0
// https://learn.microsoft.com/gaming: 0
// https://learn.microsoft.com/graph: 0
// https://learn.microsoft.com/microsoft-365: 0
// https://learn.microsoft.com/office: 0
// https://learn.microsoft.com/powershell: 0
// https://learn.microsoft.com/sql: 0
// https://learn.microsoft.com/surface: 0
// https://dotnetfoundation.org: 16
// https://learn.microsoft.com/visualstudio: 0
// https://learn.microsoft.com/windows: 0
// https://learn.microsoft.com/maui: 6
// Total: 238
// Retrieving User objects with list of IDs...
// 1: isEnabled= False
// 2: isEnabled= False
// 3: isEnabled= False
// 4: isEnabled= False
// 5: isEnabled= False
// 6: isEnabled= False
// 7: isEnabled= False
// 8: isEnabled= False
// 9: isEnabled= False
// 0: isEnabled= False
// Application ending.
Other resources
The Task asynchronous programming model (C#).
6 Collaborate with us on
GitHub .NET feedback
.NET is an open source project. Select a link to
The source for this content can be
provide feedback:
found on GitHub, where you can
also create and review issues and
Open a documentation issue
pull requests. For more
information, see our contributor
Provide product feedback
guide.