Search code examples
c#task-parallel-libraryasync-awaitc#-5.0methodimplattribute

AggressiveInlining Affects C# async Methods Behaviour


I have a static field of type ConcurrentQueue:

static readonly ConcurrentQueue<int> q = new ConcurrentQueue<int>();

and an async method:

static async Task<int?> NextNum()
{
    int? n = await Task.Run<int?>(() =>
    {
        int i = 0;

        if (q.TryDequeue(out i)) return i;
        return null;
    });

    return n;
}

Then I execute this code:

var nt = NextNum();
q.Enqueue(10);

nt.Wait();
Console.WriteLine("{0}", nt.Result.HasValue ? nt.Result.Value : -1);

And the output is 10.

Now I add MethodImpl attribute to my async method:

[System.Runtime.CompilerServices.MethodImpl(System.Runtime.CompilerServices.MethodImplOptions.AggressiveInlining)]
static async Task<int?> NextNum()
{
    int? n = await Task.Run<int?>(() =>
    {
        int i = 0;

        if (q.TryDequeue(out i)) return i;
        return null;
    });

    return n;
}

And when I execute the previously mentioned code I get -1.

Question: Does this mean in an async method the returned Task does not start immediately? And if we add MethodImpl (with AggressiveInlining) attribute it starts immediately?

I want to know if a method decorated with AggressiveInlining has any effect on task scheduler behavior.


Solution

  • Your test is nondeterministic, so the results may be different based on changes in timings / thread switches / load on the machine / number of cores / etc.

    E.g., if you change your test to:

    var nt = NextNum();
    Thread.Sleep(1000);
    q.Enqueue(10);
    

    then the output is most likely -1 even without AggressiveInlining.

    Question: Does this mean in an async method the returned Task does not start immediately? And if we add MethodImpl (with AggressiveInlining) attribute it starts immediately?

    Not at all. The task returned by NextNum always starts immediately. However, the task queued to the thread pool by Task.Run may not. That's where you're seeing the difference in behavior.

    In your original test, the task queued by Task.Run happens to take long enough that q.Enqueue gets executed before it does. In your second test, the task queued by Task.Run happens to run before q.Enqueue. Both are nondeterministic, and AggressiveInlining just changes the timings.

    Update from comments:

    I want to know if a method decorated with AggressiveInlining has any effect on task scheduler behavior.

    No, it does not.