Can the current SynchronizationContext be null?
Asked Answered
R

1

6

https://msdn.microsoft.com/en-us/magazine/gg598924.aspx

It's a great article, and I'm aware that all details could not be covered because that would essentially involve pasting the source code of .NET framework. So quoting the text:

Each thread has a current context. If "Current" is null, then the thread's current context is "new SynchronizationContext()", by convention.

On the other hand, however:

By default, the current SynchronizationContext is captured at an await point, and this SynchronizationContext is used to resume after the await (more precisely, it captures the current SynchronizationContext unless it is null, in which case it captures the current TaskScheduler)

These two statements pretty much contradict each other, so I'm thinking this is the result of some simplification that has been made by the author (I'm fine with it).

Could anyone explain this? Code that might help in answering my question (look for syncCtx variable), this piece of code is related to the second quote.

Ric answered 29/11, 2015 at 15:52 Comment(1)
It's not contradictory - the synchronization context can be null - it's just that for the purposes of continuation handling, a null synchronization context with no task scheduler is equivalent to a SynchronizationContext. "By convention" simply captures the way synchronization contexts are handled, it's not a rule that forbids a null synchronization context. As for the code, it's publicly available - referencesource.microsoft.com/#mscorlib/system/threading/….Jiles
T
2

The relevant piece of code you're looking for is inside the internal method Task.SetContinuationForAwait:

// First try getting the current synchronization context.
// If the current context is really just the base SynchronizationContext type, 
// which is intended to be equivalent to not having a current SynchronizationContext at all, 
// then ignore it.  This helps with performance by avoiding unnecessary posts and queueing
// of work items, but more so it ensures that if code happens to publish the default context 
// as current, it won't prevent usage of a current task scheduler if there is one.
var syncCtx = SynchronizationContext.CurrentNoFlow;
if (syncCtx != null && syncCtx.GetType() != typeof(SynchronizationContext))
{
    tc = new SynchronizationContextAwaitTaskContinuation(
                syncCtx, continuationAction, flowExecutionContext, ref stackMark);
}
else
{
    // If there was no SynchronizationContext, then try for the current scheduler.
    // We only care about it if it's not the default.
    var scheduler = TaskScheduler.InternalCurrent;
    if (scheduler != null && scheduler != TaskScheduler.Default)
    {
        tc = new TaskSchedulerAwaitTaskContinuation(
                scheduler, continuationAction, flowExecutionContext, ref stackMark);
    }
}

It actually does two checks, first to see that it isn't null, and the second to make sure it isn't the default SynchronizationContext, which I think is the key point here.

If you open up a Console Application and try to fetch the SynchronizationContext.Current, you'll definitely see that it can be null.

class Program
{
    public static void Main(string[] args)
    { 
        Console.WriteLine(SynchronizationContext.Current == null ? "NoContext" :
                                                                   "Context!");
    }
}
Troposphere answered 29/11, 2015 at 16:18 Comment(9)
Then how should I understand If "Current" is null, then the thread's current context is "new SynchronizationContext()", by convention.? Now I realised the author could mean the second code snippet in my previous question: #33867887Ric
I'm not sure what Stephan Cleary meant when he wrote that. Perhaps he was talking about the AsyncOperatonManager which always sets a default SynchronizationContext.Troposphere
It's two different contexts. The older article only deals with SynchronizationContext, which does have the convention of null being treated as new SynchronizationContext, e.g., for EAP components. await defines its own context which is slightly different. If you want to warp your brain, Toub has an article on even more contexts.Mccormick
@StephenCleary I see. Where can I find it? I've seen many blog posts of Stephen Toub, but probably havent' come across this one.Ric
@user4205580: Sorry; was on my new tablet when I wrote my last comment and it was painful. Seriously, the on-screen keyboard takes up like 70% of the screen! Anyway, the blog post is here.Mccormick
@StephenCleary oh, yeah, I've read that one already. You said these two are different contexts. Does it mean they both aren't of type SynchronizationContext? This method checks the CurrentNoFlow instead of Current, so yeah, they are different contexts of the same type (SynchronizationContext) - is that what you meant?Ric
@user4205580: I mean the definition of "context" is different. In the older article, I am only talking about the SynchronizationContext context (note that at that time, await did not even exist yet). In the newer quote, I'm talking about the await context, which is not a public type - it's just a concept.Mccormick
@StephenCleary One of MS developers told me that capturing of synchronization context, when using await, happens in the SetContinuationForAwait method, which is called from OnCompleted on the awaiter - code. As far as I can see, it captures the SC by checking SynchronizationContext.CurrentNoFlow, and the object returned is a SynchronizationContext, so I'm confused right now.Ric
@user4205580: Yes; the context that await uses is the current SynchronizationContext or TaskScheduler, and it's right there in the code. If there's a current SynchronizationContext, then the await uses a(n internal) type SynchronizationContextAwaitTaskContinuation, and if it's a TaskScheduler, then the await uses a(n internal) type TaskSchedulerAwaitTaskContinuation.Mccormick

© 2022 - 2024 — McMap. All rights reserved.