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.

user4205580
  • 564
  • 5
  • 25
  • 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 - http://referencesource.microsoft.com/#mscorlib/system/threading/synchronizationcontext.cs,8b34a86241c7b423. – Luaan Nov 29 '15 at 16:09

1 Answers1

1

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!");
    }
}
Yuval Itzchakov
  • 146,575
  • 32
  • 257
  • 321
  • 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: http://stackoverflow.com/questions/33867387/why-is-synchronizationcontext-checked-for-null – user4205580 Nov 29 '15 at 16:48
  • 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`. – Yuval Itzchakov Nov 29 '15 at 17:00
  • 1
    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. – Stephen Cleary Nov 29 '15 at 19:24
  • @StephenCleary I see. Where can I find it? I've seen many blog posts of Stephen Toub, but probably havent' come across this one. – user4205580 Nov 29 '15 at 22:04
  • 1
    @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](http://blogs.msdn.com/b/pfxteam/archive/2012/06/15/executioncontext-vs-synchronizationcontext.aspx). – Stephen Cleary Nov 30 '15 at 02:43
  • @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](http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,f4ecbcb6398671fb,references) checks the `CurrentNoFlow` instead of `Current`, so yeah, they are different contexts of the same type (`SynchronizationContext`) - is that what you meant? – user4205580 Nov 30 '15 at 12:08
  • @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. – Stephen Cleary Nov 30 '15 at 12:58
  • @StephenCleary One of MS developers [told me](http://stackoverflow.com/questions/33764787/does-implementation-change-the-behaviour-of-await) that capturing of synchronization context, when using await, happens in the `SetContinuationForAwait` method, which is called from `OnCompleted` on the awaiter - [code](http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs,f4ecbcb6398671fb,references). 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. – user4205580 Nov 30 '15 at 14:37
  • @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`. – Stephen Cleary Nov 30 '15 at 17:58