What is the conceptual difference between SynchronizationContext and TaskScheduler
Asked Answered
S

3

49

Stephen Toub blogged that

Both SynchronizationContext and TaskScheduler are abstractions that represent a “scheduler”, something that you give some work to, and it determines when and where to run that work. There are many different forms of schedulers. For example, the ThreadPool is a scheduler: you call ThreadPool.QueueUserWorkItem to supply a delegate to run, that delegate gets queued, and one of the ThreadPool’s threads eventually picks up and runs that delegate. Your user interface also has a scheduler: the message pump.

So System.Reactive.Concurrency.EventLoopScheduler, Dispatcher, ThreadPool, TaskScheduler, SyncrhonizationContext, and IScheduler implementations of Reactive Extensions are all "schedulers" in that sense.

What is the difference between them?

Why were they all necessary? I think I get EventLoop, Dispatcher, ThreadPool. IScheduler are also well explained.
But TaskScheduler and SyncrhonizationContext still not clear to me.

Stephen Cleary's excellent article explains SyncrhonizationContext, and I think I get it. Why then we needed TaskScheduler, is not clear.

Please explain or point to a source.

Submerse answered 6/3, 2012 at 8:16 Comment(1)
There might be a number of reasons, as Answers suggest. One another that is not mentioned yet I found in this MSDN Blog article: blogs.msdn.com/b/pfxteam/archive/2012/01/20/10259082.aspx It says that SyncrhonizationContext.Post asyncronous method does not provide notification when the work item is executed. The article suggests how to add an extension method that uses TaskCompletionSource to return a Task.Submerse
S
17

Each platform has it's own "scheduler" and they have their own abstractions around them. e.g. WinForms uses a message pump. WPF uses another message pump abstracted within "Dispatcher". A ThreadPool is another "scheduler" abstracted within "ThreadPool". These (and some others) are lower-level schedulers.

A Task and a TaskScheduler would like the user of a Task to not have to think about scheduling tasks at these lower levels (you can of course, in an abstracted way). You should be able to start a task and an ambient "scheduler" should take care of it. For example, TaskFactory.StartNew(()=>{LengthyOperation()}) should work regardless of what platform I'm running under. That's where a SynchronizationContext comes in. It knows about what lower-level schedulers are involved in the currently running framework. That is passed along to a TaskScheduler and that scheduler can both schedule tasks (possibly on to the ThreadPool) and schedule continuations through the lower-level scheduler associated with the currently running framework (see SynchronizationContext) to maintain synchronization requirements. e.g. although you'd like your Task to run in the ThreadPool, you may want a continuation to run in the UI thread.

It's important to know that the TaskScheduler is a abstraction of multiple other schedulers. This isn't the only reason it exists, but one of the reasons for this "extra" abstraction".

Smokedry answered 15/4, 2013 at 17:14 Comment(7)
SynchronizationContext comes in. It knows about what lower-level schedulers are involved in the currently running framework - if it knows that, why do we need TaskScheduler? For instance: All delegates queued to the WindowsFormsSynchronizationContext are executed one at a time; they’re executed by a specific UI thread in the order they were queued. Source If a SynchronizationContext can schedule tasks for execution, then I still can't see the need for TaskScheduler.Haskell
That is passed along to a TaskScheduler - what is passed to TaskScheduler?Haskell
TaskScheduler is abstract, so technically nothing is passed to it. It's the implementations of TaskScheduler that would accept information passed to them. What they need to function is dependent on each implementation though. For the two TaskScheduler implementations in the Fx now, you don't create them yourself, you accept a singleton of one of them. In general you don't care what is passed to these. Maybe I've missed the point of your question though; if so, could you please clarify?Smokedry
There's an easy explanation here that I think clarified the topic a bit - blogs.msdn.com/b/pfxteam/archive/2009/09/22/9898090.aspx It looks like there's nothing special about TaskScheduler itself, it just makes the code cleaner - instead of passing delegates to SynchronizationContext, which is the thing that knows the details of the current platform, we can use TaskScheduler to simply pass tasks to it. Not sure if it agrees with your interpretation though.Haskell
Also: The default TaskScheduler acts like the default SynchronizationContext, queuing the tasks to the ThreadPool. sourceHaskell
Can you point me to an example for although you'd like your Task to run in the ThreadPool, you may want a continuation to run in the UI thread.?Baecher
@Baecher Anything you need to do with the UI (like change a control value, or change what is visible) then you need to do that on the UI thread.Smokedry
P
30

I was just reading CLR via C# book by Jeffrey Ritcher and thanks to him I can also give some easy explanation related to that topic. (assuming that I am not fully agreed with the whole details in answers)

First of all, TaskScheduler object is responsible for executing scheduled tasks. The FCL ships with two TaskScheduler-derived types: the thread pool task scheduler and a synchronization context task scheduler. By default, all applications use the thread pool task scheduler. This task scheduler schedules tasks to the thread pool’s worker threads. You can get a reference to the default task scheduler by querying TaskScheduler’s static Default property.

The synchronization context task scheduler is typically used for applications sporting a graphical user interface. This task scheduler schedules all tasks onto the application’s GUI thread so that all the task code can successfully update UI components like buttons, menu items, and so on. The synchronization context task scheduler does not use the thread pool at all. You can get a reference to a synchronization context task scheduler by querying TaskScheduler’s static FromCurrentSynchronizationContext method.

As you can see from SynchronizationContextTaskScheduler implementation, internally it uses SynchronizationContext field. FCL defines a base class, called System.Threading.SynchronizationContext, which solves all these problems:

  • GUI applications impose a threading model where the thread that created a UI element is the only thread allowed to update that UI element. This is a problem, because your code will throw an exception if it tries to update UI elements via a thread pool thread. Somehow, the thread pool thread must have the GUI thread update the UI elements.
  • ASP.NET applications allow any thread to do whatever it wants. When a thread pool thread starts to process a client’s request, it can assume the client’s culture, allowing the web server to return culture-specific formatting for numbers, dates, and times. In addition, the web server can assume the client’s identity, so that the server can access only the resources that the client is allowed to access. When a thread pool thread spawns an asynchronous operation, it may be completed by another thread pool thread, which will be processing the result of an asynchronous operation. While this work is being performed on behalf of the original client request, the culture and identity needs to “flow” to the new thread pool thread so any additional work done on behalf of the client is performed using the client’s culture and identity information.

Simply stated, a SynchronizationContext-derived object connects an application model to its threading model. The FCL defines several classes derived from SynchronizationContext, but usually you will not deal directly with these classes; in fact, many of them are not publicly exposed or documented.

For the most part, application developers do not need to know anything about the SynchronizationContext class. When you await a Task, the calling thread’s SynchronizationContext object is obtained. When a thread pool thread completes the Task, the SynchronizationContext object is used, ensuring the right threading model for your application model. So, when a GUI thread awaits a Task, the code following the await operator is guaranteed to execute on the GUI thread as well, allowing that code to update UI elements. For an ASP.NET application, the code following the await operator is guaranteed to execute on a thread pool thread that has the client’s culture and principal information associated with it.

You can, of course, define your own class derived from TaskScheduler if you have special task scheduling needs. Microsoft has provided a bunch of sample code for tasks and includes the source code for a bunch of task schedulers in the Parallel Extensions Extras package. Like, IOTaskScheduler, LimitedConcurrencyLevelTaskScheduler, OrderedTaskScheduler, PrioritizingTaskScheduler, ThreadPerTaskScheduler.

Peripheral answered 10/3, 2019 at 7:23 Comment(0)
S
17

Each platform has it's own "scheduler" and they have their own abstractions around them. e.g. WinForms uses a message pump. WPF uses another message pump abstracted within "Dispatcher". A ThreadPool is another "scheduler" abstracted within "ThreadPool". These (and some others) are lower-level schedulers.

A Task and a TaskScheduler would like the user of a Task to not have to think about scheduling tasks at these lower levels (you can of course, in an abstracted way). You should be able to start a task and an ambient "scheduler" should take care of it. For example, TaskFactory.StartNew(()=>{LengthyOperation()}) should work regardless of what platform I'm running under. That's where a SynchronizationContext comes in. It knows about what lower-level schedulers are involved in the currently running framework. That is passed along to a TaskScheduler and that scheduler can both schedule tasks (possibly on to the ThreadPool) and schedule continuations through the lower-level scheduler associated with the currently running framework (see SynchronizationContext) to maintain synchronization requirements. e.g. although you'd like your Task to run in the ThreadPool, you may want a continuation to run in the UI thread.

It's important to know that the TaskScheduler is a abstraction of multiple other schedulers. This isn't the only reason it exists, but one of the reasons for this "extra" abstraction".

Smokedry answered 15/4, 2013 at 17:14 Comment(7)
SynchronizationContext comes in. It knows about what lower-level schedulers are involved in the currently running framework - if it knows that, why do we need TaskScheduler? For instance: All delegates queued to the WindowsFormsSynchronizationContext are executed one at a time; they’re executed by a specific UI thread in the order they were queued. Source If a SynchronizationContext can schedule tasks for execution, then I still can't see the need for TaskScheduler.Haskell
That is passed along to a TaskScheduler - what is passed to TaskScheduler?Haskell
TaskScheduler is abstract, so technically nothing is passed to it. It's the implementations of TaskScheduler that would accept information passed to them. What they need to function is dependent on each implementation though. For the two TaskScheduler implementations in the Fx now, you don't create them yourself, you accept a singleton of one of them. In general you don't care what is passed to these. Maybe I've missed the point of your question though; if so, could you please clarify?Smokedry
There's an easy explanation here that I think clarified the topic a bit - blogs.msdn.com/b/pfxteam/archive/2009/09/22/9898090.aspx It looks like there's nothing special about TaskScheduler itself, it just makes the code cleaner - instead of passing delegates to SynchronizationContext, which is the thing that knows the details of the current platform, we can use TaskScheduler to simply pass tasks to it. Not sure if it agrees with your interpretation though.Haskell
Also: The default TaskScheduler acts like the default SynchronizationContext, queuing the tasks to the ThreadPool. sourceHaskell
Can you point me to an example for although you'd like your Task to run in the ThreadPool, you may want a continuation to run in the UI thread.?Baecher
@Baecher Anything you need to do with the UI (like change a control value, or change what is visible) then you need to do that on the UI thread.Smokedry
K
14

Although, as quoted,

Both SynchronizationContext and TaskScheduler are abstractions that represent a “scheduler”

IMO, degree of abstraction (and hence API) differs. SynchronizationContext is a more generic API in a sense that Post/Send takes a simple method delegate.

On the other hand, TaskScheduler is an abstraction that is specific to TPL - so it offers methods such as QueueTask which deals with Task object. Using synchronization context instead of task-scheduler (i.e. having a TPL specific implementation of SynchronizationContext) would have made it more tedious to work with task scheduling (and of course, it would be a weakly typed API in context of TPL). So TPL designers have chosen to model an abstract scheduler API which make sense for TPL (that's the purpose of abstraction anyway - right?) - of course, to bridge the gap, FCL contains an internal class SynchronizationContextTaskScheduler that is wrapper TaskScheduler implementation over SynchronizationContext.

SynchronizationContext was introduced in .NET 2.0 while TPL was introduced in .NET 4. Its interesting to think what FCL designers would have chosen if the sequence was other way round i.e. what if TPL had existed at the time of .NET 2.0. IMO, TaskScheduler could have been used instead of SynchrinizationContext by modelling delgates as task in specific specialization.

Key answered 6/3, 2012 at 9:31 Comment(4)
Thanks Vinay. I also assumed that one was simly newer then the other. What still baffles me is that every async library seems to insist on desinging its own abstraction, even though they belong to the same generation. Look at the Rx (Reactive Extensions), which is also NET 4. They got IScheduler and a number of implementations. Nothing to do with Task. And this time it is not an abstract class but an interface. Given that multiplicity of libraries, each with its own strengths, but hugely overlapping none the less, I wish there would be a book, or an article sorting it all out.Submerse
@MichaelKariv, AFAIK, Rx is not part of FCL - its a library developed & released after .NET 4. It may or may not be added to next version of Fx. Said that, I believe that you will keep seeing multiple abstractions of same concept simply because one abstraction may not fit very nicely with all scenarios (fitment not from functionality perspective but rather interface perspective)Key
SynchronizationContext is more about requirements about execution affinity. For example, if you want to update a control, you need to do that on a UI thread. That requirement is abstracted within a SychronizationContext. How individual tasks are scheduled in relation to one another and where they are run (for which a Synchronization may be needed) is abstracted within a TaskScheduler. Related, but orthogonal abstractions.Smokedry
@PeterRitchie: so, SynchroContext is about "where", and Scheduler is about "where" and "what order". Hence, they are not orthogonal. If Scheduler were only about "what order", they they'd be really orthogonal. As they are, they just overlap. I personally like to view SynCtx as "lower-level" "case-specific" building block for implementing a (Task)Scheduler. But not orthogonal!Connieconniption

© 2022 - 2024 — McMap. All rights reserved.