Welcome to Shaun Luttin's public notebook. It contains rough, practical notes. The guiding idea is that, despite what marketing tells us, there are no experts at anything. Sharing our half-baked ideas helps everyone. We're all just muddling thru. Find out more about our work at bigfont.ca.

High-level notes on the SynchronizationContext

Tags: .net, c#, tasks, async-await

DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT DRAFT

Questions to Answer

  1. How do we safely do a fire-and-forget with async/await?
  2. On what thread does the SynchronizationContext itself run?
  3. How do we access the SynchronizationContext?
  4. How does the SynchronizationContext differ from the IAsyncStateMachine?
  5. What state does the SynchronizationContext capture?

Historical Context

ISynchronizeInvoke standardizes use of the Windows Message Queue.

  • queues work to a thread
  • checks whether synchronization is necessary
  • lets a source thread queue a delegate to a target thread
  • if source is already target, does not bother queuing
  • source can optionally wait for delegate completion

SynchronizationContext replaces it to support ASP.NET async pages.

  • queues work to a context (NOT to a thread)
  • does NOT check whether synchronization is necessary
  • maintains a count of outstanding operations
  • every thread has an current context

SynchronizationContext Implementations

WindowsFormsSynchronizationContext & DispatcherSynchronizationContext

  • execute delegates on a single thread
  • execute delegates one at a time
  • execute delegates in queued order

Default SynchronizationContext

  • executes delegates on any thread from the process's thread pool
  • execute delegates one at a time
  • execute delegates in queued order

AspNetSynchronizationContext

  • execute delegates on any thread from the process's thread pool
  • execute delegates one at a time
  • execute delegates in queued order
  • maintains the identity & culture of the initial thread

The Task Parallel Library (TPL)

  • uses Task objects as units of work
  • executes tasks with the TaskScheduler
  • the behavior of the default TaskScheduler...
  • matches the behavior of the default SynchronizationContext
    • any thread
    • any time
    • any order

async/await

  • in addition to creating a state machine (de-compiled here),
  • the await point captures the current SynchronizationContext
    • unless the current context is null
    • in which case it captures the current TaskScheduler
  • ConfigureAwait(bool)
    • true try to use the captured context to run the continuation
    • false not to use the captured context to run the continuation

Marshalling among Threads with Async/Await

  • to marshal means "to transform data from one form to another"
  • in the context of threading, to marshal means
    • to transform a unit of work, and
    • to send it to another thread
  • await asks for the current SynchronizationContext
  • then on continuation,
  • if the current context was null:
    • await runs the operation in the original TaskScheduler
  • else:
    • await posts the operation to the captured context
  • the decision of where/when to run the operation belongs to the implementation of the SynchronizationContext and TaskScheduler.

Schedulers

Give a scheduler some work and the scheduler determines:

  • when to run that work
  • where to run that work

Some schedulers:

  • message pump (aka message loop, event loop, run loop)
  • System.Threading.ThreadPool
  • System.Threading.SynchronizationContext
  • System.Threading.Tasks.TaskScheduler
  • System.Reactive.Concurrency.EventLoopScheduler
  • System.Reactive.Concurrency.IScheduler
  • System.Windows.Threading.Dispatcher

SynchronizationContext

  • is an abstract class that represents a scheduler
  • has several virtual methods
  • e.g. SynchronizationContext.Post()
    • accepts a delegate and
    • decides when/where to run it

TaskScheduler

  • uses the ThreadPool to queue and execute work
  • the ThreadPool receives Tasks as short-lived units of work

ThreadPool

  • maintains a global work queue
    • it is FIFO
    • it receives top-level tasks
    • it de-queues work to the next available thread
  • maintains local work queues
    • it is LIFO (i.e. like a stack)
    • it receives nested tasks
    • it is specific to the parent task's thread

White Elephant

  • an ExecutionContext contains a current SynchronizationContext
  • the SynchronizationContext, among other things, determines the thread on which to run a delegate
  • delegate / unit of work / operation are vaguely synonymous

References

https://msdn.microsoft.com/magazine/gg598924.aspx

http://stackoverflow.com/questions/5600761/what-is-marshalling-what-is-happening-when-something-is-marshalled

https://blogs.msdn.microsoft.com/pfxteam/2012/01/20/await-synchronizationcontext-and-console-apps/

https://msdn.microsoft.com/library/system.threading.tasks.taskscheduler.aspx