Task Asynchronous Pattern as you already know from my article, is one of the major change of next generation .NET applications. As this is already discussed in detail in the article, I will not repeat the same again here in this post. If you don't know what is it, please go ahead and read the article. In this post, I will try to use reflection to invoke our own async method.
So to start, let us take a look at one of the simplest Async method.
static void Main(string[] args) { Program p = new Program(); Task t = p.TryCallAsync(10000); t.Wait(); Console.ReadLine(); } public async Task TryCallAsync(int delay) { Console.WriteLine("TaskEx.Delay is called : will wait for {0} milliseconds", delay); await TaskEx.Delay(delay); Console.WriteLine("Finished execution Return to the caller"); }
In this code I have just put a delay of 10 seconds. Now after the execution is finished, another message will be printed on the screen.
We use t.Wait to ensure that the program don't terminate before ending the actual call. Now if I change the code a bit :
static void Main(string[] args) { Program p = new Program(); MethodInfo info = p.GetType().GetMethod("TryCallAsync"); Console.WriteLine("Name of the Method : {0}", info.Name); Task t = p.TryCallAsync(1000); t.Wait(); Console.ReadLine(); } public async Task TryCallAsync(int delay) { var method = MethodInfo.GetCurrentMethod(); Console.WriteLine("TaskEx.Delay is called : will wait for {0} milliseconds, methodName = {1}", delay, method.Name); await TaskEx.Delay(delay); Console.WriteLine("Finished execution Return to the caller, methodName = {0}", method.Name); }
In this code I am trying to show the current MethodName using Reflection. Oh my god!!! What I see is this :
So basically what you see is :
- We get TryCallAsync before it is called.
- From within the method body we always get MoveNext.
Yes, basically the code you write and the code that is actually getting executed is totally different. I will not cover what is written inside the code that is actually executing, I have showed all that before in my article, for the moment just remember there is a separate class that is created for you which parts the method into more than one method based on one which is called before and the one which is called after it. The Object is called as a State Machine.
Lets play with reflection a bit more to infer what I am saying to you :
static void Main(string[] args) { Program p = new Program(); MethodInfo info = p.GetType().GetMethod("TryCallAsync"); Console.WriteLine("Name of the Method : {0}", info.Name); Task<MethodBase> t = p.TryCallAsync(1000); t.Wait(); MethodBase method = t.Result; method.Invoke(p, null); Console.ReadLine(); } public async Task<MethodBase> TryCallAsync(int delay) { var method = MethodInfo.GetCurrentMethod(); Console.WriteLine("TaskEx.Delay is called : will wait for {0} milliseconds, methodName = {1}", delay, method.Name); await TaskEx.Delay(delay); Console.WriteLine("Finished execution Return to the caller, methodName = {0}", method.Name); return method; }
So I just returned the MethodInfo object from the async method body and tried to call it. If I ran the code, the code generates an exception :
So what it suggests is that the Method is actually a part of some other type. Now lets get what the type is :
static void Main(string[] args) { Program p = new Program(); MethodInfo info = p.GetType().GetMethod("TryCallAsync"); Console.WriteLine("Name of the Method : {0}", info.Name); Task<MethodBase> t = p.TryCallAsync(1000); t.Wait(); MethodBase method = t.Result; Type methodType = method.ReflectedType; object type = Activator.CreateInstance(methodType); method.Invoke(type, null); Console.ReadLine(); } public async Task<MethodBase> TryCallAsync(int delay) { var method = MethodInfo.GetCurrentMethod(); Console.WriteLine("TaskEx.Delay is called : will wait for {0} milliseconds, methodName = {1}", delay, method.Name); await TaskEx.Delay(delay); Console.WriteLine("Finished execution Return to the caller, methodName = {0}", method.Name); return method; }
Oops, it still producing exception. Seems like the Type does not contains a default constructor.
To go further, lets try to grab one constructor from the type.
var constructor = methodType.GetConstructors().First();
Upon checking the constructors it seems the Type has only one constructor with one integer argument. Lets pass any random number as parameter and try to call the MoveNext method:
Type methodType = method.ReflectedType; object type = Activator.CreateInstance(methodType, new object[]{ 0 }); method.Invoke(type, null);
Now it will call the method body, but cannot return the methodInfo object. Even if I pass anything other than 0 as argument the method actually fails to return. Why?
Actually the first argument that it looks for is the state. It checks whether the state it finds in the class is actually 0, otherwise it will only call the return statement.
So what is builder object. Just after a certain amount of research I have found its an object of AsyncTaskMethodBuilder<>. So lets pass our builder now :
static void Main(string[] args) { Program p = new Program(); MethodInfo info = p.GetType().GetMethod("TryCallAsync"); Console.WriteLine("Name of the Method : {0}", info.Name); Task<MethodBase> t = p.TryCallAsync(1000); t.Wait(); MethodBase method = t.Result; Type methodType = method.ReflectedType; object type = Activator.CreateInstance(methodType, new object[]{ 0 }); var builder = AsyncTaskMethodBuilder<MethodBase>.Create(); // pass the builder var fieldBuilder = methodType.GetField("$builder"); fieldBuilder.SetValue(type, builder); //Passing the delay var fieldDelay = methodType.GetField("delay"); fieldDelay.SetValue(type, 1000); method.Invoke(type, null); Console.ReadLine(); } public async Task<MethodBase> TryCallAsync(int delay) { var method = MethodInfo.GetCurrentMethod(); Console.WriteLine("TaskEx.Delay is called : will wait for {0} milliseconds, methodName = {1}", delay, method.Name); await TaskEx.Delay(delay); Console.WriteLine("Finished execution Return to the caller, methodName = {0}", method.Name); return method; }
You can see I have to put a lot of effort to actually pass the builder to the Type. Now when I see the output it seem to be :
Hence the second call does not actually invoke the last few lines. Err. We have to do bit more to actually invoke our method. Lets see what we are missing.
Upon further research, I found that we need to pass a value for another field (called <>t__MoveNextDelegate). Strange isnt it? I will explain why we need it in this post later.
So we have to pass the method MoveNext directly in this field. So it looks like :
static void Main(string[] args) { Program p = new Program(); MethodInfo info = p.GetType().GetMethod("TryCallAsync"); Console.WriteLine("Name of the Method : {0}", info.Name); Task<MethodBase> t = p.TryCallAsync(1000); t.Wait(); MethodBase method = t.Result; Type methodType = method.ReflectedType; object type = Activator.CreateInstance(methodType, new object[]{ 0 }); var builder = AsyncTaskMethodBuilder<MethodBase>.Create(); var fieldBuilder = methodType.GetField("$builder"); fieldBuilder.SetValue(type, builder); var fieldDelay = methodType.GetField("delay"); fieldDelay.SetValue(type, 1000); var movenextdelegate = methodType.GetField("<>t__MoveNextDelegate"); movenextdelegate.SetValue(type, new Action(() => method.Invoke(type, null))); method.Invoke(type, null); Console.ReadLine(); } public async Task<MethodBase> TryCallAsync(int delay) { var method = MethodInfo.GetCurrentMethod(); Console.WriteLine("TaskEx.Delay is called : will wait for {0} milliseconds, methodName = {1}", delay, method.Name); await TaskEx.Delay(delay); Console.WriteLine("Finished execution Return to the caller, methodName = {0}", method.Name); return method; }
Voila!! We did it finally. Now let me explain the facts.
So three things that you need to remember when you are calling an async method.
- The Call to your method is not actually a normal call, rather it will orchestrate itself into a number of calls each divided into parts based on await statement it finds in the code.
- AsyncTaskMethodBuilder is actually a wrapper of Task objects. Such that when it finds an Await it evaluates a Task which is wrapped around this Builder object. The object then returns the result to the caller.
- There is an underlying Type been created dynamically that holds the logical state machine. The compiler is smart enough to create the type appropriately.
And finally, after such an efforts, if you use :
Program p = new Program(); MethodInfo info = p.GetType().GetMethod("TryCallAsync"); info.Invoke(p, new object[] { 1000 });
It does the same thing Ha ha. So you don't need to remember this complexity, and everything will be handled automatically by the compiler itself. :)
You can download the Source from here.
I hope you like this post.
Feel free to put your comments.
Thanks.
No comments:
Post a Comment
Please make sure that the question you ask is somehow related to the post you choose. Otherwise you post your general question in Forum section.