I'm working with an unmanaged library that mandates that all calls to its API is run on the same thread. We want to use the Reactive extensions's EventLoopScheduler
to facilitate that since we'll be using Observable for other things.
I'm using a method similar to the Run
method in the code sample below to execute code in the scheduler which will always run on the same thread. When I'm working with managed code this works as expected and all calls are run on the thread managed by the event loop and before / after the async call is the main thread.
But, when I call a P/Invoke (the one in the code sample is just an example, I'm not really calling this one in my code but the behavior is the same), the thread does run on the event loop thread, but so does everything after!
I've tried adding ConfigureAwait(true)
(and false
) but it doesn't change anything. I'm really confused by this behavior, why would calling a P/Invoke change the thread continuing after the await !!?
Here's the code to reproduce:
[DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
private static extern int MessageBox(IntPtr hWnd, string lpText, string lpCaption, uint uType);
public static Task Run(Action action, IScheduler scheduler)
{
return Observable.Start(action, scheduler).SingleAsync().ToTask();
}
public static string ThreadInfo() =>
$"\"{Thread.CurrentThread.Name}\" ({Thread.CurrentThread.ManagedThreadId})";
private static async Task Main(string[] args)
{
var scheduler = new EventLoopScheduler();
Console.WriteLine($"Before managed call on thread {ThreadInfo()}");
await Run(() => Console.WriteLine($"Managed call on thread {ThreadInfo()}"), scheduler);
Console.WriteLine($"After managed call on thread {ThreadInfo()}");
Console.WriteLine($"Before PInvoke on thread {ThreadInfo()}");
await Run(() => MessageBox(IntPtr.Zero, $"Running on thread {ThreadInfo()}", "Attention", 0), scheduler);
Console.WriteLine($"After PInvoke on thread {ThreadInfo()}");
}
The execution returns something like this:
Before managed call on thread "" (1)
Managed call on thread "Event Loop 1" (6)
After managed call on thread "" (1)
Before PInvoke on thread "" (1)
Message box displayed with text: Running on thread "Event Loop 1" (6)
After PInvoke on thread "Event Loop 1" (6)
Where I expected
Before managed call on thread "" (1)
Managed call on thread "Event Loop 1" (6)
After managed call on thread "" (1)
Before PInvoke on thread "" (1)
Message box displayed with text: Running on thread "Event Loop 1" (6)
After PInvoke on thread "" (1)