65.9K
CodeProject is changing. Read more.
Home

How to Await an Asynchronous Method in Emit Code

starIconstarIcon
emptyStarIcon
starIcon
emptyStarIconemptyStarIcon

2.64/5 (3 votes)

Jan 2, 2021

CPOL

1 min read

viewsIcon

6014

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:

        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.

        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

       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()

       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

       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