C# Asynchronous programming

C# Asynchronous programming

What is Async?

Async is a C# reference, to indicate that a method, lambda expression, or anonymous method is asynchronous, use the async modifier. A method or expression is referred to as an async method if this modifier is applied to it. The async C# method ExampleMethodAsync is defined in the example that follows:

public async Task<int> ExampleMethodAsync()
{
    //...
}

Read the introduction in Asynchronous programming with async and await if you’re new to asynchronous programming or don’t know how an async method utilizes the await operator to perform potentially lengthy work without blocking the caller’s thread. The HttpClient is called by the code described below, which is contained inside an async method. Method called GetStringAsync:

string contents = await httpClient.GetStringAsync(requestUrl);

Until it encounters its first await statement, an async method executes synchronously. After that, the method is suspended until the task that is being anticipated has finished. Control then passes back to the method’s caller, as demonstrated by the example in the next section.

If an await expression or statement is absent from the method that the async keyword alters, the method operates synchronously. Any async methods without await statements trigger a compiler warning, alerting you to the possibility of an error. Refer to CS4014, Compiler Warning (level 1).

Async is a contextual keyword, meaning it can only be used to modify methods, lambda

C# async and C# await

The async and await keywords in C# help make asynchronous programming highly common these days. The entire application will have to wait to complete the operation when working with UI and using a long-running method that will take a long time, such as reading a huge file, on button click. In other words, if any process in a synchronous application becomes blocked, the entire application also becomes halted, and our application ceases to function until the entire task has been completed.

In this situation, asynchronous programming is highly useful. The application can carry on with tasks that don’t depend on the task’s completion by employing asynchronous programming.

With the aid of the async and await keywords, we will achieve all the advantages of conventional asynchronous programming with a great deal less work.

 

Let’s say we have two methods, Method1 and Method2, that are independent of one another and that Method1 takes a long time to perform its task. Synchronous programming starts with Method1 and waits for it to finish before moving on to Method2, which is then executed. Even if both procedures are independent of one another, the process will take a lot of time.

 

By utilizing simple thread programming, we can run all the functions concurrently, but this would block the user interface while we wait for the tasks to be finished. In traditional programming, we would need to write a lot of code to solve this issue, but if we utilize the async and await keywords, we can solve the issue with a lot less code.

We’ll see other examples as well, and in those cases when Method3 depends on Method1, it will use the await keyword to wait for Method1 to finish running.

In C#, async and await are code markers that indicate where control should return when a task has been completed.

Start by using real-world examples to help you grasp the concept of programming.

Await async: Await function is used to wait for the promise. It could be used within the async block only. It makes the code wait until the promise returns a result. It only makes the async block wait.

Code exemples on Async await C#:

We are going to use a console application for our demonstration in this case.

 Sample:

class Program
{
static void Main(string[] args)
{
Method1();
Method2();
Console.ReadKey();
}
public static async Task Method1()
{
await Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(" Method 1");
// Do something
Task.Delay(100).Wait();
}
});
}
public static void Method2()
{
for (int i = 0; i < 25; i++)
{
Console.WriteLine(" Method 2");
// Do something
Task.Delay(100).Wait();
}
}
}

Method 1 and Method 2 in the code above are independent of one another, and we are calling from the Main method.

Here, it is obvious that Method 1 and Method 2 are not waiting on one another.

Result:

C# async

With regard to the second illustration, let’s say Method 3 is dependent on Method 1.

Example no.2

In this illustration, Method 1 returns the overall length as an integer value, and Method 3 receives a parameter named length that is passed from Method 1 as a value.

Before giving a parameter to Method 3, we must use the await keyword in this case, and to do so, we must use the async keyword from the calling method.

Async cannot be used in the Main method for the console application if C# 7 or below is being used because it will result in the error detailed below.

C# async error

A new method called callMethod will be created, and in this method, all of our methods will be called Method 1, Method 2, and Method 3, correspondingly.

Code Sample in C# no.3:

 callMethod();
Console.ReadKey();
}
public static async void callMethod()
{
Task<int> task = Method1();
Method2();
int count = await task;
Method3(count);
}
public static async Task<int> Method1()
{
int count = 0;
await Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(" Method 1");
count += 1;
}
});
return count;
}
public static void Method2()
{
for (int i = 0; i < 25; i++)
{
Console.WriteLine(" Method 2");
}
}
public static void Method3(int count)
{
Console.WriteLine("Total count is " + count);
}
}

Code Sample in C# no.4:

class Program
{
static void Main(string[] args)
{
class Program
{
static async Task Main(string[] args)
{
await callMethod();
Console.ReadKey();
}
public static async Task callMethod()
{
Method2();
var count = await Method1();
Method3(count);
}
public static async Task<int> Method1()
{
int count = 0;
await Task.Run(() =>
{
for (int i = 0; i < 100; i++)
{
Console.WriteLine(" Method 1");
count += 1;
}
});
return count;
}
public static void Method2()
{
for (int i = 0; i < 25; i++)
{
Console.WriteLine(" Method 2");
}
}
public static void Method3(int count)
{
Console.WriteLine("Total count is " + count);
}
}

The return type of Method 1 is the only parameter Method3 in the code above needs. In this case, the await keyword is essential for waiting for Method 1 task completion.

Result:

Async programming sample

Actual instance:

There are a few.NET Framework 4.5 supporting APIs, and the Windows runtime has methods that support async programming.

 

With the aid of the async and await keywords, we can leverage all of these in real-time projects to speed up task completion.

HttpClient, SyndicationClient, StorageFile, StreamWriter, StreamReader, XmlReader, MediaCapture, BitmapEncoder, BitmapDecoder, etc. are a few APIs that include async functions.

In this example, we’ll asynchronously read every character from a sizable text file to determine how long each character is.

Code Sample:

class Program
{ ke
static void Main()
{
Task task = new Task(CallMethod);
task.Start();
task.Wait();
Console.ReadLine();
int length = await task;
Console.WriteLine(" Total length: " + length);
Console.WriteLine(" After work 1");
Console.WriteLine(" After work 2");
}
Console.WriteLine(" File reading is completed");
return length;
}
}static async Task<int> ReadFile(string file)
{
int length = 0;
Console.WriteLine(" File reading is stating");
using (StreamReader reader = new StreamReader(file))
{
// Reads all characters from the current position to the end of the stream asynchronously
// and returns them as one string.
string s = await reader.ReadToEndAsync();
length = s.Length;
}
}

The code above calls the ReadFile function to read a text file’s contents and determine how many characters there altogether in the text file.

It will take a long time to read all the characters in our sampleText.txt file because there are too many of them.

 

Since we are utilizing async programming to read the entire contents of the file, the subsequent lines of code will be executed without waiting for the method’s return value. Because we are using the await keywords and we are going to use the return value for the line of code provided below, it must still wait for that line of code.

int length = await task;
Console.WriteLine(" Total length: " + length);

Then, more lines of code will be executed consecutively one after the other.

Console.WriteLine(" After work 1");
Console.WriteLine(" After work 2");

Result:

C# Async example

I/O-bound example: Download data from a web service

When a button is pressed, you might need to download some data from a web service, but you don’t want to obstruct the UI thread. It can be done in the following way:

private readonly HttpClient _httpClient = new HttpClient();
downloadButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI as the request
// from the web service is happening.
//
// The UI thread is now free to perform other work.
var stringData = await _httpClient.GetStringAsync(URL);
DoSomethingWithData(stringData);
};

Without becoming mired down in interactions with Task objects, the code above states the intention (asynchronous data download).

CPU-bound example: Perform a calculation for a game

Imagine you’re creating a mobile game where hitting a button will deal harm to a lot of the enemies you see. The damage calculation can be expensive, and running it on the UI thread would appear to pause the game while it is being done.

Starting a background thread that uses Task to complete the task is the best method to manage this.

Run and use await to check the outcome. As the job is being done, this enables the user interface to feel fluid.

private DamageResult CalculateDamageDone()
{
// Code omitted:
//
// Does an expensive calculation and returns
// the result of that calculation.
}
calculateButton.Clicked += async (o, e) =>
{
// This line will yield control to the UI while CalculateDamageDone()
// performs its work. The UI thread is free to perform other work.
var damageResult = await Task.Run(() => CalculateDamageDone());
DisplayDamage(damageResult);
};

This code represents the button’s click event’s objective clearly, doesn’t call for manually managing a background thread, and does it in a non-blocking manner.

Key thing to keep in mind and understand:

  • I/O-bound and CPU-bound code can both employ async code, although in slightly different ways depending on the situation.
  • TaskT> and Task are constructs used in async programs to model work being done in the background.
  • The await keyword can be used in a method’s body since the async keyword converts it into an async method.
  • The await keyword suspends the calling method and returns control to the calling method until the task that is being awaited is finished.
  • Only async methods may utilize the await keyword.

How can you recognize CPU-bound and I/O-bound work

This guide’s first two examples demonstrated how to utilize async and await for CPU- and I/O-bound tasks, respectively. Knowing whether a task is I/O-bound or CPU-bound is essential since it can have a significant impact on your code’s performance and possibly result in the misuse of some constructs.

Before writing any code, you should consider the following two questions:

  • Is something going to be “waiting” for in your code, like data from a database?

Your job is I/O-bound if you replied “yes.”

  • Will your code need to run a pricey calculation?

Your task is CPU-bound if you indicated “yes” in your response.

If your work is task-bound by I/O, utilize async and await instead of Task. Run. Use of the Task Parallel Library is not advised.

If your workload is CPU-bound and responsiveness is important to you, utilize async and await, but use Task to spawn the work off into a different thread.

Run. Consider using the Task Parallel Library as well, if concurrency and parallelism are acceptable for the work.

Additionally, you should constantly monitor how your code is being executed. For instance, you might discover that the cost of your CPU-bound task is insufficiently high relative to the multithreading overhead associated with context shifts. Every decision has a trade-off, and you should make the best trade-off possible given your circumstances.

Important advices and information

  • Await must be a body keyword in async methods else they will never yield!

It’s crucial to remember this. The C# compiler generates a warning if await is not used in the body of an async method, but the code still compiles and executes as if it were a regular method. This is highly wasteful because the state machine the C# compiler created for the async method does nothing.

  • Every name you create for an async method should end with “Async.”

In.NET, this convention is used to more clearly distinguish between synchronous and asynchronous methods. It’s not always necessary to use methods that aren’t explicitly used by your code, such event handlers or web controller methods. Being specific in their nomenclature isn’t as crucial because they aren’t invoked directly by your code.

  • Only event handlers should utilize async void.

Because asynchronous event handlers cannot use Task and TaskT> because events lack return types, async void is the only way to enable them to function. Any other application of async void departs from the TAP paradigm and poses a challenge, such as:

  • An async void method cannot catch an exception that is thrown outside of it.
  • Testing async void methods is challenging.
  • If the caller does not anticipate them to be async, async void methods may have undesirable side effects.

 

  • When utilizing async lambdas in LINQ expressions, proceed with caution.

Deferred execution is used by lambda expressions in LINQ, which means that code execution could occur when you least expect it to. If not written properly, the addition of blocking jobs to this can quickly lead to a deadlock. Furthermore, the execution of the code may be harder to predict when asynchronous code is nested in this way. Although async and LINQ are strong together, they should be utilized with the utmost caution and clarity.

  • Anytime ValueTask is an option, use it.

Certain pathways may have performance bottlenecks as a result of async operations returning a Task object. Utilizing a task entails allocating an object because task is a reference type. The extra allocations can add up over time in performance-critical code sections if a method with the async modifier completes synchronously or returns a cached result. If those allocations take place in close loops, it can become expensive.

  • Think about use ConfigureAwait (false)

When should I utilize the Task.ConfigureAwait(Boolean) function is a frequently asked question. A Task object can configure its awaiter using this method. This is a crucial factor, and setting it improperly could have an impact on performance or possibly lead to deadlocks.

  • Make your code less stateful.

Don’t rely on the operation of specific methods or the state of global objects. Rather, just rely on the results of procedures. Why?

  • Code will be simpler to rationalize.
  • Testing code will be simpler.
  • It is much easier to mix synchronous and async code.
  • Usually, race-related situations can be completely avoided.
  • Coordination of async programming is made simple by relying on return values.
  • Dependency injection works pretty well with it.

expressions, or anonymous methods. It is treated as an identifier in all other situations.

Return Types

The subsequent return types are available for async methods:

  • Task
  • Task<TResult>
  • Since callers cannot await those methods and must design an alternative mechanism to indicate successful completion or error conditions, methods are generally avoided for programs other than event handlers.
  • Every type that has a reachable GetAwaiter function as of C# 7.0. The Program. Tasks. One example of this is the ValueTaskTResult> type. By including the NuGet package System, it is accessible. Threading.Tasks.Extensions.

The async method cannot declare any in, ref, or out parameters and cannot return a result that is a reference, but it can call methods that do.

If an operand of type TResult is specified in the method’s return statement, you must provide Task as the return type. If no useful value is returned when the method is finished, use Task. In other words, a method call returns a Task, but any await expression that was awaiting the Task evaluates to void once the Task has been finished.

The void return type is typically used to create event handlers, which need it. A void-returning async method’s caller is unable to anticipate it or catch any exceptions that it may throw.

Code Sample:

public static async Task DisplayCurrentInfoAsync()
{
await WaitAndApologizeAsync();
Console.WriteLine($"Today is {DateTime.Now:D}");
Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
Console.WriteLine("The current temperature is 76 degrees.");
}
static async Task WaitAndApologizeAsync()
{
await Task.Delay(2000);
Console.WriteLine("Sorry for the delay...\n");
}

Result:

Return async c#

Task<TResult> return type

For an async method with a return statement and TResult as the operand, the return type is TaskTResult>.

The return statement in the GetLeisureHoursAsync function in the example below returns an integer. Taskint> must be specified as the return type in the method declaration. The async function FromResult is a stand-in for an operation that returns a DayOfWeek.

public static async Task ShowTodaysInfoAsync()
{
string message =
$"Today is {DateTime.Today:D}\n" +
"Today's hours of leisure: " +
$"{await GetLeisureHoursAsync()}";
Console.WriteLine(message);
}
static async Task<int> GetLeisureHoursAsync()
{
DayOfWeek today = await Task.FromResult(DateTime.Now.DayOfWeek);
int leisureHours =
today is DayOfWeek.Saturday || today is DayOfWeek.Sunday
? 16 : 5;
return leisureHours;
}

Result:

tresult async

Void return type

Asynchronous event handlers, which need a void return type, employ the void return type. An async method that returns void cannot be anticipated, thus you should return a Task in place of it for methods other than event handlers that don’t return a value. Any caller of such a method must proceed all the way through without stopping to wait for the async method it called to finish. Any values or errors that the async method produces must not affect the caller.

Exceptions thrown by an async function that returns void cannot be caught by the method’s caller. Your program is likely to crash as a result of such unhandled exceptions. The exception is kept in the returned task if a method that returns a Task or TaskTResult> throws one. When the job is awaited, the exception is once again thrown. Ensure that calls to any async method that potentially throw an exception have an awaited return type of Task or TaskTResult>.

Code Sample:

public class NaiveButton
{
public event EventHandler? Clicked;
public void Click()
{
Console.WriteLine("Somebody has clicked a button. Let's raise the event...");
Clicked?.Invoke(this, EventArgs.Empty);
Console.WriteLine("All listeners are notified.");
}
}
public class AsyncVoidExample
{
static readonly TaskCompletionSource<bool> s_tcs = new TaskCompletionSource<bool>();
public static async Task MultipleEventHandlersAsync()
{
Task<bool> secondHandlerFinished = s_tcs.Task;
var button = new NaiveButton();
button.Clicked += OnButtonClicked1;
button.Clicked += OnButtonClicked2Async;
button.Clicked += OnButtonClicked3;
Console.WriteLine("Before button.Click() is called...");
button.Click();
Console.WriteLine("After button.Click() is called...");
await secondHandlerFinished;
}
private static void OnButtonClicked1(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 1 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 1 is done.");
}
private static async void OnButtonClicked2Async(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 2 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 2 is about to go async...");
await Task.Delay(500);
Console.WriteLine(" Handler 2 is done.");
s_tcs.SetResult(true);
}
private static void OnButtonClicked3(object? sender, EventArgs e)
{
Console.WriteLine(" Handler 3 is starting...");
Task.Delay(100).Wait();
Console.WriteLine(" Handler 3 is done.");
}
}

Result:

Void return type

Generalized async return types and ValueTask<TResult>

As a simple implementation of a task-returning value,.NET offers the System.Threading.Tasks.ValueTaskTResult> structure. You must include the System.Threading.Tasks.Extensions NuGet package in your project in order to use the System.Threading.Tasks.ValueTaskTResult> type. The value of two dice rolls is obtained using the ValueTaskTResult> structure in the example that follows.

class Program
{
static readonly Random s_rnd = new Random();
static async Task Main() =>
Console.WriteLine($"You rolled {await GetDiceRollAsync()}");
static async ValueTask<int> GetDiceRollAsync()
{
Console.WriteLine("Shaking dice...");
int roll1 = await RollAsync();
int roll2 = await RollAsync();
return roll1 + roll2;
}
static async ValueTask<int> RollAsync()
{
await Task.Delay(500);
int diceRoll = s_rnd.Next(1, 7);
return diceRoll;
}
}

Result:

Async return types