In C#, the asynchronous programming can be achieved using the async/await, Async/Await is especially important to use in .NET and .NET Core web servers if high concurrency is desired. .NET uses a managed thread pool that will only spawn up to something like 50 threads quickly by default. If it sees that more threads are needed, it will slowly add them. So in this post, I am going to explain how you can use async-await keyword in C# with example.
What is an async method?
An async method is a method that returns to the calling method before completing all its work, and then completes its work while the calling method continues its execution.
An async method has the following characteristics:
- An async method must have the async keyword in its method header, and it must be before the return type.
- This modifier doesn’t do anything more than signal that the method contains one or more await expressions.
- It contains one or more await expressions. These expressions represent tasks that can be done asynchronously.
- It must have one of the following three return types.
- void :If the calling method just wants the async method to execute, but doesn’t need any further interaction with it
- Task : If the calling method doesn’t need a return value from the async method, but needs to be able to check on the async method’s state
- Task<T> :If the calling method is to receive a value of type T back from the call, the return type of the async method must be Task
- An async method can have any number of formal parameters of any types but it cannot be out or ref parameters.
- The name of an async method should end with the suffix Async.
- Other than Methods, lambda expressions and anonymous methods can also act as async objects.
Benefit of using Async
Asynchrony is essential for activities that are potentially blocking, such as web access. Access to a web resource sometimes is slow or delayed. If such an activity is blocked in a synchronous process, the entire application must wait. In an asynchronous process, the application can continue with other work that doesn't depend on the web resource until the potentially blocking task finishes.
Let's take a look at an example using Console application:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncAwaitInCsharp
{
class Program
{
static void Main(string[] args)
{
DoAsyncWork.CalculateSumAsync(10, 11);
//do some other work.
Thread.Sleep(200);
Console.WriteLine("Program Exiting");
Console.ReadLine();
}
static class DoAsyncWork
{
public static async void CalculateSumAsync(int i1, int i2)
{
int sum = await Task.Run(() => GetSum(i1, i2));
Console.WriteLine("Value: {0}", sum);
}
private static int GetSum(int i1, int i2)
{
return i1 + i2;
}
}
}
}
Await keyword is used to call async method, which marks a point where the method can't continue until the awaited asynchronous operation is complete. In the meantime, the method is suspended, and control returns to the method's caller.
An async method typically contains one or more occurrences of an await operator, but the absence of await expressions doesn’t cause a compiler error. If an async method doesn’t use an await operator to mark a suspension point, the method executes as a synchronous method does, despite the async modifier. The compiler issues a warning for such methods.
The task to which the await operator is applied typically is returned by a call to a method that implements the Task-Based Asynchronous Pattern. They include methods that return Task, Task<TResult>, and System.Threading.Tasks.ValueType<TResult> objects.
Let consider one more example, in this example, the HttpClient.GetByteArrayAsync method returns a Task<byte[]>. The task is a promise to produce the actual byte array when the task is complete. The await operator suspends execution until the work of the GetByteArrayAsync method is complete. In the meantime, control is returned to the caller of GetPageSizeAsync. When the task finishes execution, the await expression evaluates to a byte array.
using System;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
public class AsyncAwaitInCsharp
{
public static void Main()
{
string[] args = Environment.GetCommandLineArgs();
if (args.Length > 1)
GetPageSizeAsync(args[1]).Wait();
else
Console.WriteLine("Enter at least one URL on the command line.");
}
private static async Task GetPageSizeAsync(string url)
{
var client = new HttpClient();
var uri = new Uri(Uri.EscapeUriString(url));
byte[] urlContents = await client.GetByteArrayAsync(uri);
Console.WriteLine($"{url}: {urlContents.Length/2:N0} characters");
}
}
// The following call from the command line:
// await1 http://docs.microsoft.com
// displays output like the following:
// http://docs.microsoft.com: 7,967 characters
As shown in the previous example, if await is applied to the result of a method call that returns a Task<TResult>, then the type of the await expression is TResult. If await is applied to the result of a method call that returns a Task, then the type of the await expression is void. The following example illustrates the difference.
// await keyword used with a method that returns a Task<TResult>.
TResult result = await AsyncMethodThatReturnsTaskTResult();
// await keyword used with a method that returns a Task.
await AsyncMethodThatReturnsTask();
// await keyword used with a method that returns a ValueTask<TResult>.
TResult result = await AsyncMethodThatReturnsValueTaskTResult();
An await expression does not block the thread on which it is executing. Instead, it causes the compiler to sign up the rest of the async method as a continuation on the awaited task. Control then returns to the caller of the async method. When the task completes, it invokes its continuation, and execution of the async method resumes where it left off.
An await expression can occur only in the body of its enclosing method, lambda expression, or anonymous method, which must be marked with an async modifier. The term await serves as a keyword only in that context. Elsewhere, it is interpreted as an identifier. Within the method, lambda expression, or anonymous method, an await expression cannot occur in the body of a synchronous function, in a query expression, in the block of a lock statement, or in an unsafe context.
Aysnc programming in ASP.NET MVC
Let’s take a look at the default implementation of the calls I am making to Twitter and Facebook to get the follower count and page likes respectively. Here are my 2 methods in HomeController.
private string GetTwitterFollowersCount(string Username)
{
XDocument xdoc = XDocument.Load("http://api.twitter.com/1/users/show.xml?screen_name=" + Username + "&include_entities=false");
return (from item in xdoc.Descendants("user")
select item.Element("followers_count").Value)
.SingleOrDefault();
}
private string GetFacebookLikes(string FaceBookPageURL)
{
string uri = "https://api.facebook.com/method/fql.query?query=select%20%20like_count,%20total_count,%20share_count,%20click_count%20from%20link_stat%20where%20url=%22" + FaceBookPageURL + "%22";
return Convert.ToString((from elem in XElement.Load(uri).Descendants()
where elem.Name.LocalName == "like_count"
select elem).First<XElement>().Value);
}
When I call these 2 methods one after the other, they will just get the data from a different source and render the result in the view. That’s fine as it’s my requirement. Now when I run my application it now takes a longer time to render the view. Before I explain this further take a look at my Index().
public ActionResult Index()
{
Stopwatch watch = new Stopwatch();
watch.Start();
ViewBag.TwitterFollowers = GetTwitterFollowersCount("audi");
ViewBag.FacebookLikes = GetFacebookLikes("http://facebook.com/audi");
watch.Stop();
Int64 elapsedTime = watch.ElapsedMilliseconds;
ViewBag.Time = elapsedTime.ToString();
return View();
}
This is because Controllers are synchronous by default and it will process the request one after the other i.e. it will wait for the first task to complete and then move on to the next task. In my case the controller will execute the GetFollowersCount method and only after the completion of this method it will shift to the next method GetFacebookLikes. This could be a long running task or just imagine if you are retrieving heavy data from different sources. The view or the page will not render until the execution of both the methods will not get completed.
To get a rough idea of the time elapsed in making synchronous calls in the above controller, I have used StopWatch(System.Diagnostics) class. Here is the output of the , which shows approximate time to fetch data while calling Index ActionMethod
Request completed in : 1588 millisec
It took 1588ms to complete 2 requests.
Now if I implement async in the controller I will see a drastic improvement. It is not just the controller ActionResult which I have to change to support async calls but I also have to update the 2 methods which I am using to collect the stats. Here are my updated methods.
private Task<string> GetTwitterFollowersCountAsync(string Username)
{
return Task.Run(() =>
{
XDocument xdoc = XDocument.Load("http://api.twitter.com/1/users/show.xml?screen_name=" + Username + "&include_entities=false");
return (from item in xdoc.Descendants("user")
select item.Element("followers_count").Value).SingleOrDefault();
});
}
private Task<string> GetFacebookLikesAsync(string FaceBookPageURL)
{
return Task.Run(() =>
{
string uri = "https://api.facebook.com/method/fql.query?query=select%20%20like_count,%20total_count,%20share_count,%20click_count%20from%20link_stat%20where%20url=%22" + FaceBookPageURL + "%22";
return Convert.ToString((from elem in XElement.Load(uri).Descendants()
where elem.Name.LocalName == "like_count"
select elem).First<XElement>().Value);
});
}
Take a look at the GetTwitterFollwersCountAsync and GetFacebookLikesAsync return type. Previously they were only string, and now I have changed their return type to Task. I have put the code inside Task.Run() as it will queue the specified work to run on the Thread Pool and returns a Task(TResult) handle for that work.
I have changed the ActionResult to handle the async feature. As you can see that this controller method does not return ActionResult instead it return Task.
Now, would have to modify my Index ActionMethod also, as it should be using async keyword only, because to call asycn method, callee must also be async in order to implement asynchronous programming in C# properly.
public async Task<ActionResult> Index()
{
Stopwatch watch = new Stopwatch();
watch.Start();
Task<string> twitterFollowers = GetTwitterFollowersCountAsync("audi");
Task<string> facebookLikes = GetFacebookLikesAsync("http://facebook.com/audi");
await Task.WhenAll(twitterFollowers, facebookLikes);
ViewBag.TwitterFollowers = await twitterFollowers;
ViewBag.FacebookLikes = await facebookLikes;
watch.Stop();
Int64 elapsedTime = watch.ElapsedMilliseconds;
ViewBag.Time = elapsedTime.ToString();
return View();
}
Task.WhenAll() will create a task that will complete when all of the supplied tasks have completed. This actually parallelizes the request. Let's check the output and compare both.
The difference is noticeable, 607ms! is halfway down and a huge performance benefit. This is a small example and the real performance can be noticed when you work with real scenarios.