C# async/await : concept and implementation
on the Surface: A new control structure
Background
Traditional control structures keeps code sequencial and keeps control-flow consecutive, such as If/Else/While/For/Break/Continue. They offer almost everything a developer needs. However there's one domain problem, where programmers can't freely express their logic and maintainers can't efficiently get an insight of code logic. That lies in asynchronous programming.
In some user-interactive applications(such as mobile phone apps), UI responsiveness is quite important. In order to keep UI responsive, developers use Thread/Listener(or Callback) Model to implement asynchronous programming. Although this solution performs well, it leads to code fragments, which should have been sequencialed together. If codes are logically continuous, then they should be sequencialed in presentation. If you have developed an phone app using the traditional asynchonous way, you must have suffered from code chaos and disorder. So Thread/Listener Model is in fact an obstacle.
Async/Await
A new control structure of C# comes to the rescue: Async/Await. It's "new" because it keeps code sequencial, but no longer keeps control-flow consecutive. Read below code sample.
Traditional way:
public class Listener{ public void onCompelete(String content){ Console.WriteLine("Baidu Page:"+name); } } public class Main{ public void event_handler(){ [Code Before] HttpClient client=new HttpClient(); client.getAsync("www.baidu.com", new Listener()); [Code After] } }
Async/Await
public async void event_handler(){ [Code Before] work() [Code After] }
public async void work(){ HttpClient client=new HttpClient(); String content=await client.getAsync("www.baidu.com"); Console.WriteLine("Baidu Page:"+content); }
Pay attention to the difference. In Traditional way, "Console.Write" is located in Listener, which is split away from event_handler. In Async/Aswait sample, "Console.Write" is right sequencialed behind the main logic. Definitely this is much better than former. Yet you need some time to get used to this programming model.
As previously said, Async/Await does not keep control-flow consecutive. In this sample it means when work() executes "String content=await client.getAsync("www.baidu.com")", the control flow immediately returns to event_handler, So [Code after] will go on executing without being blocked. On the other hand, "Console.WriteLine("Baidu Page:"+content);" wil not be executed until "client.getAsync" finishes.
Underground: code transformation
C# runtime does not support execution disconsecutiveness, so C# compiler has to perform code transformation before giving it to runtime engine.Transformation is like below:
Async/Await control structure => Traditional language structure
Roughly, the generated code is like below.
public void work(){
TaskAwaiter<String> awaiter=client.getAsync("www.baidu.com").GetAwaiter();
__moveNext = delegate{
Console.WriteLine(awaiter1.GetResult());
}
awaiter. OnCompleted(__moveNext);
}
If work() is defined as:
public async Task<Int> work(){ HttpClient client=new HttpClient(); String content=await client.getAsync("www.baidu.com"); Console.WriteLine("Baidu Page:"+content); return content.length; }
Then generated code is like:
public Task<Int> work(){ var __builder = new AsyncTaskMethodBuilder<Int>(); TaskAwaiter<String> awaiter=client.getAsync("www.baidu.com").GetAwaiter(); __moveNext = delegate{ Console.WriteLine(awaiter.GetResult()); __builder.SetResult(__awaiter.GetResult().length); //Set computation result, the caller method will use awaiter to fetch it } awaiter. OnCompleted(__moveNext); return __builder.Task; }
The runnable "awaiter" will be scheduled by C# runtime engine to run on some thread(To be discussed later). When it finishes, the delegate will be called.
Further More: how to return result to the caller method?
Suppose we have a caller function:
public async Task top_caller(){ int len=await work() Console.writeLine(len) }
which is transformed to:
public Task top_caller(){ TaskAwaiter<Int> awaiter=work().GetAwaiter() // == __builder.Task.GetAwaiter() __moveNext=delegate{ int len=awaiter.GetResult() //work()’s result is fetched from awaited Console.writeLine(len) } awaiter.OnComplete(__moveNext) }
Schedule runnable objects
Disclaimer: I have not spent time diving into C# internal. Here, I just "imagin" how C# managed to do runnable objects scheduling. So if there's some error in below analysis, please kindly let me know(Leave a comment here).
If you have developed an android application using handler thread, or if you have written codes using Actor Model, you already got it. The way C# schedules runnable objects is similar: every thread has a message queue that is receiving messages from others. If you want a task to be executed on a specific thread, just post it into that thread's message queue. In C#, it's named as Syncronization Context. Whatever name, handler thread/Actor Model/Syncronization Context are of the same nature.
浙公网安备 33010602011771号