Click here to Skip to main content
15,885,366 members
Articles / Programming Languages / C#
Tip/Trick

How to Await an Asynchronous Method in Emit Code

Rate me:
Please Sign up or sign in to vote.
2.64/5 (3 votes)
2 Jan 2021CPOL1 min read 5.3K   1   1
Awaiting an asynchronous method in Emit code

Preface

First of all, immediately explain why there is such a pseudo-titled Demo essay? It’s not that I have a misunderstanding of knowledge or want to mislead others because everyone knows that emit writes are synchronous methods, it is impossible to await, at least it has not provided corresponding functions for so many years.

Begin with Demo

The original method is a method that returns 55 after a delay of 2 seconds:

C#
public static async Task<int> GetV()
{
    await Task.Delay(2000);
    return 55;
}

Now we need to add 6 to the result of 55 so that the final result becomes 61.

Our test method is like this, it will output some simple time to help us understand the execution order and asynchronous situation.

C#
private static async Task Test(MethodInfo method, MethodInfo awaitMehtod)
{
    var caller = CreateCaller(method, awaitMehtod);
    Console.WriteLine($"Start {awaitMehtod.Name} at: {DateTime.Now}.");
    var task = caller();
    Console.WriteLine($"Call done at: {DateTime.Now}.");
    var number = await task;
    Console.WriteLine($"Hello {number} at: {DateTime.Now}.");
    Console.WriteLine($"End at: {DateTime.Now}.");
    Console.WriteLine();
}

1. ContinueWith

C#
public static Func<Task<int>> CreateCaller(MethodInfo method, MethodInfo awaitMehtod)
 {
     var m = new DynamicMethod(Guid.NewGuid().ToString("N"),
             typeof(Task<int>), Type.EmptyTypes);
     var il = m.GetILGenerator();
     il.Emit(OpCodes.Call, method);
     il.Emit(OpCodes.Call, typeof(Program).GetMethod
            (nameof(Program.AddSixUseContinueWith))); // Here is the difference
     il.Emit(OpCodes.Ret);

     return m.CreateDelegate(typeof(Func<Task<int>>)) as Func<Task<int>>;
 }

 public static Task<int> AddSixUseContinueWith(Task<int> task)
 {
     return task.ContinueWith(i =>
     {
         Console.WriteLine($"AddSixUseContinueWith is: {DateTime.Now}.");
         return i.Result + 6;
     });
 }

Test Results

Start AddSixUseContinueWith at: 2021/1/2 13:34:55.
Call done at: 2021/1/2 13:34:55.
AddSixUseContinueWith is: 2021/1/2 13:34:57.
Hello 61 at: 2021/1/2 13:34:57.
End at: 2021/1/2 13:34:57.

Advantage

Still really asynchronous

Disadvantage

The cost is relatively large. After all, there is no optimization such as the state machine. (The cost is at the ns level, not the ms you think.)

2. GetAwaiter().GetResult()

C#
public static Func<Task<int>> CreateCaller(MethodInfo method, MethodInfo awaitMehtod)
 {
     var m = new DynamicMethod(Guid.NewGuid().ToString("N"),
             typeof(Task<int>), Type.EmptyTypes);
     var il = m.GetILGenerator();
     il.Emit(OpCodes.Call, method);
     il.Emit(OpCodes.Call, typeof(Program).GetMethod
            (nameof(Program.AddSixUseAwaiter))); // Here is the difference
     il.Emit(OpCodes.Ret);

     return m.CreateDelegate(typeof(Func<Task<int>>)) as Func<Task<int>>;
 }

 public static Task<int> AddSixUseAwaiter(Task<int> task)
 {
     var r = task.ConfigureAwait(false).GetAwaiter().GetResult() + 6;
     Console.WriteLine($"AddSixUseAwaiter is: {DateTime.Now}.");
     return Task.FromResult(r);
 }

Test Results

Start AddSixUseAwaiter at: 2021/1/2 13:34:57.
AddSixUseAwaiter is: 2021/1/2 13:34:59.
Call done at: 2021/1/2 13:34:59.
Hello 61 at: 2021/1/2 13:34:59.
End at: 2021/1/2 13:34:59.

Advantage

Very small execution time

Disadvantage

Of course, in this way, asynchronous has become synchronous, so in some cases, we may operate improper code and lose the advantages of asynchronous methods.

3. async/await

C#
public static Func<Task<int>> CreateCaller(MethodInfo method, MethodInfo awaitMehtod)
 {
     var m = new DynamicMethod(Guid.NewGuid().ToString("N"),
             typeof(Task<int>), Type.EmptyTypes);
     var il = m.GetILGenerator();
     il.Emit(OpCodes.Call, method);
     il.Emit(OpCodes.Call, typeof(Program).GetMethod
            (nameof(Program.AddSixUseAsyncAwait))); // Here is the difference
     il.Emit(OpCodes.Ret);

     return m.CreateDelegate(typeof(Func<Task<int>>)) as Func<Task<int>>;
 }

 public static async Task<int> AddSixUseAsyncAwait(Task<int> task)
 {
     var r = await task;
     Console.WriteLine($"AddSixUseAsyncAwait is: {DateTime.Now}.");
     return r + 6;
 }

Test Results

Start AddSixUseAsyncAwait at: 2021/1/2 13:34:59.
Call done at: 2021/1/2 13:34:59.
AddSixUseAsyncAwait is: 2021/1/2 13:35:01.
Hello 61 at: 2021/1/2 13:35:01.
End at: 2021/1/2 13:35:01.

Advantage

The advantages of async / await are not lost.

Disadvantage

I originally wanted to migrate the result processing logic in emit to the async / await method, and the emit code must be well designed.

All of the Demo On

https://github.com/fs7744/grocery/blob/main/csharp/emit_await/EmitAwaitDemo/Program.cs

Sharing is not easy, if you can give a little motivation, I would be very grateful: pay attention to my open source project:Norns.Urd

History

  • 2nd January, 2021: Initial version

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
China China
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
BugWrong implementation? Pin
wmjordan3-Jan-21 13:30
professionalwmjordan3-Jan-21 13:30 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.