In this post, we will see how to manage Asynchronous Programming in C#.
Asynchronous programming is a means of parallel programming in which a unit of work runs separately from the main application thread and notifies the calling thread of its completion, failure or progress.
Obviously, there would be many things to see and understand in Asynchronous programming but, in this post, we will see the basic commands.
We start creating a Console application where we will define three functions called Funct1, Funct2 and Funct3:
[PROGRAM.CS]
using System;
using System.Threading;
namespace TestAsyncAwait
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Start method {DateTime.Now.ToLongTimeString()}");
var resultFunct1 = Funct1(10);
var resultFunct2 = Funct2(10);
var resultFunct3 = Funct3(10);
Console.WriteLine($"Funct1 result: {resultFunct1}");
Console.WriteLine($"Funct2 result: {resultFunct2}");
Console.WriteLine($"Funct3 result: {resultFunct3}");
Console.WriteLine($"End {DateTime.Now.ToLongTimeString()}");
}
static int Funct1(int input)
{
// we insert 4 seconds of wait
Thread.Sleep(4000);
return input;
}
static int Funct2(int input)
{
// we insert 4 seconds of wait
Thread.Sleep(4000);
return input * 2;
}
static int Funct3(int input)
{
// we insert 4 seconds of wait
Thread.Sleep(4000);
return input * 3;
}
}
}
If we run the application, this will be the result:
We can see the result is correct because, it tooks 12 second in order to run the three method.
Now, we will try to run the three methods in three different Tasks, in order to run them in parallel:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TestAsyncAwait
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine($"Start method {DateTime.Now.ToLongTimeString()}");
var resultFunct1 = Task.Run(() => Funct1(10));
var resultFunct2 = Task.Run(() => Funct2(10));
var resultFunct3 = Task.Run(() => Funct3(10));
Console.WriteLine($"Funct1 result: {resultFunct1}");
Console.WriteLine($"Funct2 result: {resultFunct2}");
Console.WriteLine($"Funct3 result: {resultFunct3}");
Console.WriteLine($"End {DateTime.Now.ToLongTimeString()}");
}
static int Funct1(int input)
{
// we insert 4 seconds of wait
Thread.Sleep(4000);
return input;
}
static int Funct2(int input)
{
// we insert 4 seconds of wait
Thread.Sleep(4000);
return input * 2;
}
static int Funct3(int input)
{
// we insert 4 seconds of wait
Thread.Sleep(4000);
return input * 3;
}
}
}
If we run the application, this will be the result:
It doesn’t work fine and it is not the result we expected.
The problem is that we run the methods in parallel way but, the Main Program, finishes before they return the result.
So, we will add the await key, in order to wait for the result:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TestAsyncAwait
{
class Program
{
// If we use await in a method, we have to define it as async and the result must be a Task
static async Task Main(string[] args)
{
Console.WriteLine($"Start method {DateTime.Now.ToLongTimeString()}");
var resultFunct1 = await Task.Run(() => Funct1(10));
var resultFunct2 = await Task.Run(() => Funct2(10));
var resultFunct3 = await Task.Run(() => Funct3(10));
Console.WriteLine($"Funct1 result: {resultFunct1}");
Console.WriteLine($"Funct2 result: {resultFunct2}");
Console.WriteLine($"Funct3 result: {resultFunct3}");
Console.WriteLine($"End {DateTime.Now.ToLongTimeString()}");
}
static int Funct1(int input)
{
// we insert 4 seconds of wait
Thread.Sleep(4000);
return input;
}
static int Funct2(int input)
{
// we insert 4 seconds of wait
Thread.Sleep(4000);
return input * 2;
}
static int Funct3(int input)
{
// we insert 4 seconds of wait
Thread.Sleep(4000);
return input * 3;
}
}
}
If we run the application, this will be the result:
It works fine, but it takes that same time of the sync version.
At this point, we will transform the three methods in Async methods:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TestAsyncAwait
{
class Program
{
// If we use await in a method, we have to define it as async and the result must be a Task
static async Task Main(string[] args)
{
Console.WriteLine($"Start method {DateTime.Now.ToLongTimeString()}");
var resultFunct1 = await Funct1(10);
var resultFunct2 = await Funct2(10);
var resultFunct3 = await Funct3(10);
Console.WriteLine($"Funct1 result: {resultFunct1}");
Console.WriteLine($"Funct2 result: {resultFunct2}");
Console.WriteLine($"Funct3 result: {resultFunct3}");
Console.WriteLine($"End {DateTime.Now.ToLongTimeString()}");
}
static async Task<int> Funct1(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input;
}
static async Task<int> Funct2(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 2;
}
static async Task<int> Funct3(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 3;
}
}
}
If we run the application, this will be the result:
It works, but it works like the previous version.
In order to improve the performance, we have to run the three methods in parallel:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TestAsyncAwait
{
class Program
{
// If we use await in a method, we have to define it as async and the result must be a Task
static async Task Main(string[] args)
{
Console.WriteLine($"Start method {DateTime.Now.ToLongTimeString()}");
var resultFunct1 = Funct1(10);
var resultFunct2 = Funct2(10);
var resultFunct3 = Funct3(10);
// using Task.WaitAll, the three functions will run in parallel
Task.WaitAll(resultFunct1, resultFunct2, resultFunct3);
Console.WriteLine($"Funct1 result: {resultFunct1.Result}");
Console.WriteLine($"Funct2 result: {resultFunct2.Result}");
Console.WriteLine($"Funct3 result: {resultFunct3.Result}");
Console.WriteLine($"End {DateTime.Now.ToLongTimeString()}");
}
static async Task<int> Funct1(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input;
}
static async Task<int> Funct2(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 2;
}
static async Task<int> Funct3(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 3;
}
}
}
Now, if we run the application, this will be the result:
Perfect! It works fine and it takes only 4 seconds!
Now, we will modify the code, in order to insert as input of the Funct3, the output of Funct2.
In order to do it, we will run Funct1 and Funct2 in parallel and we will run Funct3 only when Funct2 will be finished:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TestAsyncAwait
{
class Program
{
// If we use await in a method, we have to define it as async and the result must be a Task
static async Task Main(string[] args)
{
Console.WriteLine($"Start method {DateTime.Now.ToLongTimeString()}");
var resultFunct1 = Funct1(10);
var resultFunct2 = Funct2(10);
// we put as input the ouptut of Funct2
var resultFunct3 = Funct3(resultFunct2.Result);
// using Task.WaitAll, the three functions run in parallel
Task.WaitAll(resultFunct1, resultFunct2);
// we check if resultFunct2 is completed
if(resultFunct2.IsCompletedSuccessfully)
{
// we run the last function
await resultFunct3;
}
Console.WriteLine($"Funct1 result: {resultFunct1.Result}");
Console.WriteLine($"Funct2 result: {resultFunct2.Result}");
Console.WriteLine($"Funct3 result: {resultFunct3.Result}");
Console.WriteLine($"End {DateTime.Now.ToLongTimeString()}");
}
static async Task<int> Funct1(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input;
}
static async Task<int> Funct2(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 2;
}
static async Task<int> Funct3(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 3;
}
}
}
If we run the application, this will be the result:
It works fine!
Obviously, we can manage in the same time, async and sync methods:
using System;
using System.Threading;
using System.Threading.Tasks;
namespace TestAsyncAwait
{
class Program
{
// If we use await in a method, we have to define it as async and the result must be a Task
static async Task Main(string[] args)
{
Console.WriteLine($"Start method {DateTime.Now.ToLongTimeString()}");
var resultFunct0 = Funct0();
var resultFunct1 = Funct1(10);
var resultFunct2 = Funct2(10);
var resultFunct3 = Funct3(10);
var resultFunct4 = Funct4(10);
var resultFunct5 = Funct5(10);
Console.WriteLine($"Funct0 result: {resultFunct0}");
// using Task.WaitAll, the three functions run in parallel
Task.WaitAll(resultFunct1, resultFunct2, resultFunct3, resultFunct5);
Console.WriteLine($"Funct1 result: {resultFunct1.Result}");
Console.WriteLine($"Funct2 result: {resultFunct2.Result}");
Console.WriteLine($"Funct3 result: {resultFunct3.Result}");
Console.WriteLine($"Funct4 result: {resultFunct4}");
Console.WriteLine($"Funct5 result: {resultFunct5.Result}");
Console.WriteLine($"End {DateTime.Now.ToLongTimeString()}");
}
static int Funct0()
{
return 0;
}
static async Task<int> Funct1(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input;
}
static async Task<int> Funct2(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 2;
}
static async Task<int> Funct3(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 3;
}
static int Funct4(int input)
{
return input * 4;
}
static async Task<int> Funct5(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 5;
}
}
}
If we run the application, this will be the result:
The last thing that I want to show is the option WhenAny that, it helps us to know which is the task has already finished:
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
namespace TestAsyncAwait
{
class Program
{
// If we use await in a method, we have to define it as async and the result must be a Task
static async Task Main(string[] args)
{
Console.WriteLine($"Start method {DateTime.Now.ToLongTimeString()}");
var resultFunct0 = Funct0();
var resultFunct1 = Funct1(10);
var resultFunct2 = Funct2(10);
var resultFunct3 = Funct3(10);
var resultFunct4 = Funct4(10);
var resultFunct5 = Funct5(10);
Console.WriteLine($"Funct0 result: {resultFunct0}");
var lstTasks = new List<Task> { resultFunct1, resultFunct2, resultFunct3, resultFunct5 };
// we run something until into lstTask there will be some Task
while(lstTasks.Count>0)
{
// we take the first task finished
Task finishedTask = await Task.WhenAny(lstTasks);
// we check which Task is
if (finishedTask == resultFunct1)
{
Console.WriteLine("Funct1 is done");
}
else if (finishedTask == resultFunct2)
{
Console.WriteLine("Funct2 is done");
}
else if (finishedTask == resultFunct3)
{
Console.WriteLine("Funct3 is done");
}
else if (finishedTask == resultFunct5)
{
Console.WriteLine("Funct5 is done");
}
// we remove the finished task from the list
lstTasks.Remove(finishedTask);
}
Console.WriteLine($"Funct1 result: {resultFunct1.Result}");
Console.WriteLine($"Funct2 result: {resultFunct2.Result}");
Console.WriteLine($"Funct3 result: {resultFunct3.Result}");
Console.WriteLine($"Funct4 result: {resultFunct4}");
Console.WriteLine($"Funct5 result: {resultFunct5.Result}");
Console.WriteLine($"End {DateTime.Now.ToLongTimeString()}");
}
static int Funct0()
{
return 0;
}
static async Task<int> Funct1(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input;
}
static async Task<int> Funct2(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 2;
}
static async Task<int> Funct3(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 3;
}
static int Funct4(int input)
{
return input * 4;
}
static async Task<int> Funct5(int input)
{
// we insert 4 seconds of wait
await Task.Delay(4000);
return input * 5;
}
}
}
If we run the application, this will be the result: