Asynchronous Code
IO Bound Non-blocking code
The Asynchronous Programming Patterns (.NET) allows for non-blocking code.Essentially by setting up a state machine and continuing to run from the calling thread.
Starting with a simple example:
class Program {
static void Main(string[] args) {
Run();
// do something else here (that takes about 2 secs)
for(int i=0; i<20; i++) {
Console.Write(".");
Thread.Sleep(100);
}
Console.WriteLine("\r\nPress any key to exit...");
Console.ReadKey();
}
static async void Run() {
Console.WriteLine("Blocked by synchronous call");
Console.WriteLine("The result from the synchronous call is {0}", GetCharacterCount());
Console.WriteLine("Not blocked");
Console.WriteLine("The result from the asynchronous call is {0}", await GetCharacterCountAsync());
}
static int GetCharacterCount() {
var client = new HttpClient();
var page = client.GetStringAsync("https://www.dotnetfoundation.org").Result;
return page.Length;
}
static async Task GetCharacterCountAsync() {
var client = new HttpClient();
var page = await client.GetStringAsync("https://www.dotnetfoundation.org");
return page.Length;
}
}
The output looks like:
static void Main(string[] args) {
Run();
// do something else here (that takes about 2 secs)
for(int i=0; i<20; i++) {
Console.Write(".");
Thread.Sleep(100);
}
Console.WriteLine("\r\nPress any key to exit...");
Console.ReadKey();
}
static async void Run() {
Console.WriteLine("Blocked by synchronous call");
Console.WriteLine("The result from the synchronous call is {0}", GetCharacterCount());
Console.WriteLine("Not blocked");
Console.WriteLine("The result from the asynchronous call is {0}", await GetCharacterCountAsync());
}
static int GetCharacterCount() {
var client = new HttpClient();
var page = client.GetStringAsync("https://www.dotnetfoundation.org").Result;
return page.Length;
}
static async Task
var client = new HttpClient();
var page = await client.GetStringAsync("https://www.dotnetfoundation.org");
return page.Length;
}
}
Blocked by synchronous call
The result from the synchronous call is 30873
Not blocked
.................The result from the asynchronous call is 30873
...
Press any key to exit...
What has happened here?The result from the synchronous call is 30873
Not blocked
.................The result from the asynchronous call is 30873
...
Press any key to exit...
The first call, GetCharacterCount, returns the character count of the text from the website.
GetCharacterCountAsync does the same.
The former blocked the calling thread until the text was returned.
The latter has not blocking the calling thread, and waits for a callback.
Therefore, the non-blocking method GetCharacterCountAsync, allowed the calling thread to continue processing.
CPU Asynchronous Code
Another form of asynchronous programming, according to Microsoft's definition , is CPU bound work.Defined as different to the IO Bound work, in that, a task is assigned to the CPU and callsback after processing ends.
A task is spawned, using Task.Run()and the OS queues the task.
The task runs concurrently and any result returned to the main calling thread.
An example of this is:
class Program {
static void Main(string[] args) {
Run();
// do something else here (that takes about 2 secs)
for(int i=0; i<20; i++) {
Console.Write(".");
Thread.Sleep(100);
}
Console.WriteLine("\r\nPress any key to exit...");
Console.ReadKey();
}
static async void Run() {
Console.WriteLine("The expensive calculation result is {0}", await CalculateResult());
}
static async Task CalculateResult() {
int x = 0;
// This queues up the work on the threadpool.
var expensiveResultTask = Task.Run(() => {
for (int i = 0; i < 10; i++) {
x += i;
Thread.Sleep(100);
}
return x;
});
// Execution of CalculateResult is yielded here!
return await expensiveResultTask;
}
}
The output looks like:
static void Main(string[] args) {
Run();
// do something else here (that takes about 2 secs)
for(int i=0; i<20; i++) {
Console.Write(".");
Thread.Sleep(100);
}
Console.WriteLine("\r\nPress any key to exit...");
Console.ReadKey();
}
static async void Run() {
Console.WriteLine("The expensive calculation result is {0}", await CalculateResult());
}
static async Task
int x = 0;
// This queues up the work on the threadpool.
var expensiveResultTask = Task.Run(() => {
for (int i = 0; i < 10; i++) {
x += i;
Thread.Sleep(100);
}
return x;
});
// Execution of CalculateResult is yielded here!
return await expensiveResultTask;
}
}
..........The expensive calculation result is 45
..........
Press any key to exit...
Note the Task.Run, in the CalculateResult method...........
Press any key to exit...
Then, yielding using the await, which starts the extra resources.
It's important to remember that another CPU resource is used, which could be more expensive
Mentioned in Microsoft's website is this type of concurrency should not used for tight loops.
Summary
The .NET Async patterns are very simple to use, just add the async keyword to a method and either call a resource with the
await keyword or use the Task.Run method (and then await).
One just has to remember
- if looking for a resource from another server to use the Non-blocking pattern.
- if doing CPU intensive work to use the CPU Asynchronous pattern.