A: background
1. Tell a story
The knowledge of “await” and “async” has been said to be very bad, it seems that there is nothing to say, but I find that many articles still theoretically describe the usage of these two grammatical sugars. People who know or understand, and those who don’t seem to understand but don’t understand after a few days, life is like a play, it is not good to rely on memorization. In fact, in essence, await and async are just syntactic sugar at compiler level, which will be prototyped at IL level, so it is very necessary to understand these two syntactic sugar at this level.
Ii. Understanding from IL level
1. Download using WebClient
In order to make it easy to type back to the prototype, I first use an example, using webClient asynchronously download http://cnblogs.com HTML, the code is as follows:
class Program
{
static void Main(string[] args)
{
var html = GetResult();
Console.WriteLine("Wait a minute... Downloading cnblogs -> HTML \r\n");
var content = html.Result;
Console.WriteLine(content);
}
static async Task<string> GetResult()
{
var client = new WebClient();
var content = await client.DownloadStringTaskAsync(new Uri("http://cnblogs.com"));
returncontent; }}Copy the code
The above code is very simple and you can see that the asynchronous operation does not block the main thread output: wait… Cnblogs -> HTML \r\n, there is nothing to say at the compiler level. What happens at the IL level?
2. Mining await Async IL code
As usual, ilSpy goes, as shown below:
As you can see, there is a GetResult method, a Main method, and a
d__1 class that pops up out of nowhere. Let’s talk about it one by one.
< 1 > < GetResult > d__1 > class
Because it came out of nowhere, it was very interesting, so what’s its IL?
.class nested private auto ansi sealed beforefieldinit '<GetResult>d__1'
extends [System.Runtime]System.Object
implements [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine
{
.method private final hidebysig newslot virtual
instance void MoveNext () cil managed{}.method private final hidebysig newslot virtual
instance void SetStateMachine (
class [System.Runtime]System.Runtime.CompilerServices.IAsyncStateMachine stateMachine
) cil managed{}}Copy the code
The
d__1 class implements the interface IAsyncStateMachine, defined as follows:
If the MoveNext looks familiar, you’ll be using this method in foreach collections, which were called enumerations at the time, but have been modified to call the state machine ๐๐๐.
<2> GetResult ()
To illustrate, I’ll simplify the IL code in the method body:
.method private hidebysig static
class [System.Runtime]System.Threading.Tasks.Task1 < `string> GetResult(a)cil managed
{
IL_0000: newobj instance void ConsoleApp3.Program/'<GetResult>d__1'::.ctor()
IL_0005: stloc. 0
IL_0006: ldloc. 0
IL_0007: call valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<!0> valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Create()
IL_000c: stfld valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1': :'<>t__builder'
IL_0011: ldloc. 0
IL_0012: ldc.i4.m1
IL_0013: stfld int32 ConsoleApp3.Program/'<GetResult>d__1': :'<>1__state'
IL_0018: ldloc. 0
IL_0019: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string> ConsoleApp3.Program/'<GetResult>d__1': :'<>t__builder'
IL_001e: ldloca.s 0
IL_0020: call instance void valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder`1<string>::Start<class ConsoleApp3.Program'< /GetResult>d__1'> (!!!!! 0 &)IL_0025: ldloc.0
IL_0026: ldflda valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder1 < `string> ConsoleApp3.Program'< /GetResult>d__1':' < >t__builder'
IL_002b: call instance class [System.Runtime]System.Threading.Tasks.Task` 1 <! 0 >valuetype [System.Threading.Tasks]System.Runtime.CompilerServices.AsyncTaskMethodBuilder1 < `string> : :get_Task(a)IL_0030: ret
} // end of method Program::GetResult
Copy the code
If you know a little bit about newobj at IL_0000, you should know that this method just does new
d__1 and returns a get_Task() from IL_002b. Then you should know why the main thread is not blocked. It makes sense that the HTTP result is hidden in the Task
.
<3> Main
The Main method remains the same as it was before.
Three: write IL back to C#
1. Complete C# code
From the previous section you should have a framework understanding of IL level with await and async. Here I will write all C# code backwards:
class Program
{
static void Main(string[] args)
{
var html = GetResult();
Console.WriteLine("Wait a minute... Downloading cnblogs -> HTML \r\n");
var content = html.Result;
Console.WriteLine(content);
}
static Task<string> GetResult()
{
GetResult stateMachine = new GetResult();
stateMachine.builder = AsyncTaskMethodBuilder<string>.Create();
stateMachine.state = - 1;
stateMachine.builder.Start(ref stateMachine);
returnstateMachine.builder.Task; }}class GetResult : IAsyncStateMachine
{
public int state;
public AsyncTaskMethodBuilder<string> builder;
private WebClient client;
private string content;
private string s3;
private TaskAwaiter<string> awaiter;
public void MoveNext()
{
var result = string.Empty;
TaskAwaiter<string> localAwaiter;
GetResult stateMachine;
int num = state;
try
{
if (num == 0)
{
localAwaiter = awaiter;
awaiter = default(TaskAwaiter<string>);
num = state = - 1;
}
else
{
client = new WebClient();
localAwaiter = client.DownloadStringTaskAsync(new Uri("http://cnblogs.com")).GetAwaiter();
if(! localAwaiter.IsCompleted) { num = state =0;
awaiter = localAwaiter;
stateMachine = this;
builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine);
return;
}
}
s3 = localAwaiter.GetResult();
content = s3;
s3 = null;
result = content;
}
catch (Exception exx)
{
state = 2 -;
client = null;
content = null;
builder.SetException(exx);
}
state = 2 -;
client = null;
content = null;
builder.SetResult(result);
}
public void SetStateMachine(IAsyncStateMachine stateMachine){}}Copy the code
As you can see, once written in C# code it should run without any problems, so let me draw a flow chart to make it easier to understand.
With xMind above, the basic flow is as follows: stateMachine.builder.Start(ref stateMachine) -> GetResult.MoveNext -> client.DownloadStringTaskAsync -> localAwaiter.IsCompleted = false -> builder.AwaitUnsafeOnCompleted(ref localAwaiter, ref stateMachine) -> GetResult.MoveNext -> localAwaiter.GetResult() -> builder.SetResult(result)
2. Analyze the AsyncTaskMethodBuilder
In fact, if you look carefully, you will find that the so-called await and async operations are carried by AsyncTaskMethodBuilder, such as starting asynchronous tasks, marshalling HTML results, contacting the underlying IO, Where Task
corresponds to AsyncTaskMethodBuilder
, Task corresponds to AsyncTaskMethodBuilder, This is why the compiler keeps telling you to return Task and Task
in async, otherwise you won’t find the corresponding AsyncTaskMethodBuilder, right?
Then focus on the AwaitUnsafeOnCompleted method, which is very important and is commented as follows:
//
// Summary:
// Schedules the state machine to proceed to the next action when the specified
// awaiter completes. This method can be called from partially trusted code.
public void AwaitUnsafeOnCompleted"[NullableAttribute(0)] TAwaiter[NullableAttribute(0)] TStateMachine> (ref TAwaiter awaiter, ref TStateMachine stateMachine)
where TAwaiter : ICriticalNotifyCompletion
where TStateMachine : IAsyncStateMachine;
Copy the code
Once this method is called, it waits for the underlying IO to call getresult. MoveNext again, indicating either an exception or completion of the Task, and the Awaiter wrapped Task result is marshaled to Builder.setresult.
Then, a brief description of the state machine is given. Through debugging, it will be found that there will be two MoveNext steps, one start, one take the result.
<1> The first callback to MoveNext
First MoveNext triggered by the stateMachine. Builder. Start (ref the stateMachine), can use dnspy to be debugged, below:
<2> The second callback to MoveNext
The second MoveNext triggered by the builder. AwaitUnsafeOnCompleted (ref localAwaiter, ref the stateMachine), As you can see, once the network driver has finished processing, the IO thread of the thread pool will initiate the task to the MoveNext in the final trigger code, and finally to the result of the awaiter task, as shown below:
Four:
Syntax sugar can be simple or complex, but don’t be afraid of complex ones. Learn to translate IL code into C#, and you will see a lot of things you didn’t understand before, won’t you?