Does ConfigureAwait affect non threadpool threads only?

I am playing a little bit with ConfigureAwait because I would like to understand how it works.

Therfore, I wrote the following small console application (actually running in LINQPad):

void Main()
{
    // Use dispatcher to execute the code on STA thread
    Dispatcher.CurrentDispatcher.Invoke(() => Run());
}

private async static Task Run()
{
    var continueOnCapturedContext1 = true;
    var continueOnCapturedContext2 = true;

    PrintThreadID();
    await Task.Run(() => PrintThreadID()).ConfigureAwait(continueOnCapturedContext1);
    PrintThreadID();
    await Task.Run(() => PrintThreadID()).ConfigureAwait(continueOnCapturedContext2);   
    PrintThreadID();
}

private static void PrintThreadID()
{
    Console.Write(Thread.CurrentThread.ManagedThreadId.ToString("00") + "\t");
}

And I got the following output:

A) true/true

var continueOnCapturedContext1 = true;
var continueOnCapturedContext2 = true;

1) 11 19 11 07 11

2) 11 09 11 12 11

3) 11 06 11 06 11

Expected: dispatcher thread (11) was captured and awaitable tasks were executed on different or same threadpool threads.

B) false/false

var continueOnCapturedContext1 = false;
var continueOnCapturedContext2 = false;

1) 11 23 23 22 22

2) 11 19 19 19 19

3) 11 10 10 10 10

Also expected: SynchronizationContext was not captured, so subsequent awaitable and non-awaitable code were run on threadpool thread (usually the same).

C) false/true

var continueOnCapturedContext1 = false;
var continueOnCapturedContext2 = true;

1) 11 14 14 06 06

2) 11 20 20 20 20

3) 11 17 17 08 08

The result of output 1 and 3 is strange. The 2. awaitbale task was executed with option "continue on captured context" so I would expect that it runs on the same thread as the code that called it.

It seems, that ConfigureAwait(true/false) has no effect on subsequent awaitable calls if it was alreay called before, right?

Jon Skeet
people
quotationmark

The 2. awaitbale task was executed with option "continue on captured context" so I would expect that it runs on the same thread as the code that called it.

That assumes that "context == thread", but it doesn't. The synchronization context that uses the thread-pool will resume on any thread in the thread-pool. Now if you don't capture the synchronization context, you'll still end up on "a thread in the thread pool".

So yes, if you're already on a thread-pool thread, it won't matter whether or not you capture the synchronization context... the continuation will still end up on a thread pool thread. But it's worth pointing out that it would be entirely reasonable to have a different synchronization context with multiple threads, and capturing the synchronization context would return to one of those threads, but not necessarily the same one.

It seems, that ConfigureAwait(true/false) has no effect on subsequent awaitable calls if it was already called before, right?

Not quite. That will be the case if the task needs to use the continuation. If the first task you call ConfigureAwait on has already completed, the code will continue to execute synchronously - so at that point, the second ConfigureAwait is important.

Example:

using System;
using System.Windows.Forms;
using System.Threading;
using System.Threading.Tasks;

class Test
{    
    static void Main()
    {
        var form = new Form();
        form.Load += async (sender, args) =>
        {
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            await Task.FromResult(10).ConfigureAwait(false);
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
            await Task.Delay(1000).ConfigureAwait(false);
            Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
        };
        Application.Run(form);
    }
}

Sample output:

1
1
5

So the second Console.WriteLine showed that the code was still running on the same thread despite the ConfigureAwait(false), because Task.FromResult returns an already-completed task.

people

See more on this question at Stackoverflow