question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Unnecessary thread change during the execution of a sync method which is being advised by an async advice

See original GitHub issue

Hi,

I found a problem with advising a sync method with an async advice. The problem is that currently an async advice (which implements the IMethodAsyncAdvice interface) unnecessarily changes the current thread after the execution of the method, in spite of the implementation of the async advice being purely sync (i.e. the advice itself does not do anything async, it just simply proceeds with the advised method).

So currently the following example:

    class Program
    {
        static async Task Main(string[] args)
        {
            WriteLine($"MyProudMethod BEFORE {Thread.CurrentThread.ManagedThreadId}");
            MyProudMethod();
            WriteLine($"MyProudMethod AFTER {Thread.CurrentThread.ManagedThreadId}");

            WriteLine();

            WriteLine($"MyProudMethodAsync BEFORE {Thread.CurrentThread.ManagedThreadId}");
            await MyProudMethodAsync();
            WriteLine($"MyProudMethodAsync AFTER {Thread.CurrentThread.ManagedThreadId}");
        }

        [MyProudAdvice]
        static void MyProudMethod()
        {
            WriteLine($"MyProudMethod {Thread.CurrentThread.ManagedThreadId}");
        }

        [MyProudAdvice]
        static async Task MyProudMethodAsync()
        {
            WriteLine($"MyProudMethodAsync BEGIN {Thread.CurrentThread.ManagedThreadId}");
            await Task.Delay(1);
            WriteLine($"MyProudMethodAsync END {Thread.CurrentThread.ManagedThreadId}");
        }
    }

    public class MyProudAdvice : Attribute, IMethodAsyncAdvice
    {
        public async Task Advise(MethodAsyncAdviceContext context)
        {
            WriteLine($"Async Advise BEGIN: {context.TargetMethod.Name} {Thread.CurrentThread.ManagedThreadId}");

            await context.ProceedAsync();

            WriteLine($"Async Advise END: {context.TargetMethod.Name} {Thread.CurrentThread.ManagedThreadId}");
        }
    }

shows this output (I edited it, so it includes my comments):

MyProudMethod BEFORE 1
Async Advise BEGIN: MyProudMethod 1
MyProudMethod 1
Async Advise END: MyProudMethod 3    // unnecessary thread change!
MyProudMethod AFTER 1   // here we are back to the original thread

MyProudMethodAsync BEFORE 1
Async Advise BEGIN: MyProudMethodAsync 1
MyProudMethodAsync BEGIN 1
MyProudMethodAsync END 4
Async Advise END: MyProudMethodAsync 4
MyProudMethodAsync AFTER 4

The problem goes away after fixing MrAdvice’s Tasks.Void method by changing its implementation from this:

        public static Task Void()
        {
            if (_void == null)
            {
                var @void = new Task(() => { });
                @void.Start();
                _void = @void;
            }
            return _void;
        }

to this:

        public static Task Void()
        {
            if (_void == null)
            {
                var tcsVoid = new TaskCompletionSource<object>();
                tcsVoid.SetResult(null);
                _void = tcsVoid.Task;
            }
            return _void;
        }

Of course, the best solution would be to use .NET’s Task.CompletedTask method, but I know that it does not exist in .NET 4.0, that is why Tasks.Void had to be introduced.

After the fix the previous example shows this output, where there are no unnecessary thread changes during the execution of the sync method:

MyProudMethod BEFORE 1
Async Advise BEGIN: MyProudMethod 1
MyProudMethod 1
Async Advise END: MyProudMethod 1
MyProudMethod AFTER 1

MyProudMethodAsync BEFORE 1
Async Advise BEGIN: MyProudMethodAsync 1
MyProudMethodAsync BEGIN 1
MyProudMethodAsync END 4
Async Advise END: MyProudMethodAsync 4
MyProudMethodAsync AFTER 4

Issue Analytics

  • State:closed
  • Created 6 years ago
  • Comments:6 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
picrapcommented, Nov 1, 2017

Finally released! Thanks again!

0reactions
davidnemeticommented, Nov 1, 2017

It was some kind of weaving related error: the building of the test projects failed, because it did not find the build/MrAdvice.Weaver.exe (despite that it was definitely there) or the task inside (unfortunately, I do not remember the details), and it said that I should check the UsingTask element.

I think that the VS restart and solution reload solved the problem eventually.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Why do I get a warning when trying to run an async method ...
The warning states: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying ...
Read more >
Understanding Async, Avoiding Deadlocks in C# | by Eke ...
Threads just as in any OS represent execution of code. ... async functions from sync function using this method, then update the UI...
Read more >
Async/Await: is it possible to start a Task on @MainActor ...
Hello,. I could never start a Task on the @MainActor in a synchronous way, even if the task is started from the main...
Read more >
Advanced Tips for Using Task.Run with Async/Await
Don't Continue on the Main Thread Unnecessarily​​ As you probably recall, await captures information about the current thread when used with Task. Run...
Read more >
Async, Await, and ConfigureAwait – Oh My!
By default, when you use async/await, it will resume on the original thread that started the request. However, if another long-running process ......
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found