'Why does SynchronizationContext.Post() get called only once when using multiple awaits?
Consider the following example:
async Task DoWork()
{
await Task.Run(() =>
{
for (int i = 0; i < 25; i++)
{
Console.WriteLine("Task run 1: " + Thread.CurrentThread.ManagedThreadId);
}
});
// The SynchronizationContext.Post() gets called after Run 1 and before Run 2
await Task.Run(() =>
{
for (int i = 0; i < 25; i++)
{
Console.WriteLine("Task run 2: " + Thread.CurrentThread.ManagedThreadId);
}
});
// I expect it to run after Run 2 and before Run 3 as well but it doesn't
await Task.Run(() =>
{
for (int i = 0; i < 25; i++)
{
Console.WriteLine("Task run 3: " + Thread.CurrentThread.ManagedThreadId);
}
});
}
I would expect a call to SynchronizationContext.Post() to be made every time an await operation ends but after overriding the Post() like this
public class MySynchronizationContext
{
public override void Post(SendOrPostCallback d, object? state)
{
Console.WriteLine("Continuation: " + Thread.CurrentThread.ManagedThreadId);
base.Post(d, state);
}
}
Installed like this at the very start of Main()
SynchronizationContext.SetSynchronizationContext(new MySynchronizationContext());
It only prints the message once, after the first Run() is completed.
I assumed that's because Task.Run() may detect that it's being called on a threadpool thread and just reuse the current thread but that seems not to be the case because some of my tests resulted in Run 2 and Run 3 running on different threads.
Why does the completion of an awaited Task only runs after the first await?
Solution 1:[1]
I ended up figuring it out on my own.
The problem seemed to be my invalid understanding of capturing current SynchronizationContext by the await.
async Task DoWork()
{
// This is still in the main thread so SynchronizationContext.Current
// returns an instance of MySynchronizationContext which this
// await captures.
await Task.Run(() =>
{
for (int i = 0; i < 25; i++)
{
Console.WriteLine("Task run 1: " + Thread.CurrentThread.ManagedThreadId);
}
});
// Here it uses the captured MySynchronizationContext to call
// the .Post() method. The message gets printed to the console and
// continuation gets put on the ThreadPool
// This await tries to capture current SynchronizationContext but
// since we're on the ThreadPool's thread, SynchronizationContext.Current
// returns null and it uses the default implementation
// instead of MySynchronizationContext. This is why my message from
// the overriden .Post() doesn't get printed which made me believe
// that it didn't call .Post() at all. It did, just not my .Post()
await Task.Run(() =>
{
for (int i = 0; i < 25; i++)
{
Console.WriteLine("Task run 2: " + Thread.CurrentThread.ManagedThreadId);
}
});
// .Post() gets called on the default SynchronizationContext
// Again, we're on the ThreadPool's thread,
// so the default SynchronizationContext gets captured
await Task.Run(() =>
{
for (int i = 0; i < 25; i++)
{
Console.WriteLine("Task run 3: " + Thread.CurrentThread.ManagedThreadId);
}
});
}
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 |
