¿Cuál es la diferencia conceptual entre SynchronizationContext y TaskScheduler?

¿Cuál es la diferencia conceptual entre SynchronizationContext y TaskScheduler?

Estaba leyendo CLR via C# libro de Jeffrey Ritcher y gracias a él también puedo dar una explicación fácil relacionada con ese tema. (asumiendo que no estoy completamente de acuerdo con todos los detalles en las respuestas)

En primer lugar, TaskScheduler El objeto es responsable de ejecutar las tareas programadas. El FCL se envía con dos TaskScheduler -tipos derivados:el programador de tareas del grupo de subprocesos y un programador de tareas de contexto de sincronización . De forma predeterminada, todas las aplicaciones utilizan el programador de tareas del grupo de subprocesos. Este programador de tareas programa tareas para los subprocesos de trabajo del grupo de subprocesos. Puede obtener una referencia al programador de tareas predeterminado consultando TaskScheduler Default estático propiedad.

El programador de tareas de contexto de sincronización se usa normalmente para aplicaciones que tienen una interfaz gráfica de usuario. Este programador de tareas programa todas las tareas en el subproceso de la GUI de la aplicación para que todo el código de la tarea pueda actualizar correctamente los componentes de la IU, como botones, elementos de menú, etc. El programador de tareas del contexto de sincronización no utiliza el grupo de subprocesos en absoluto. Puede obtener una referencia a un programador de tareas de contexto de sincronización consultando TaskScheduler FromCurrentSynchronizationContext estático de método.

Como puede ver en SynchronizationContextTaskScheduler implementación, internamente usa SynchronizationContext campo. FCL define una clase base, llamada System.Threading.SynchronizationContext , que resuelve todos estos problemas:

  • Las aplicaciones GUI imponen un modelo de subprocesamiento en el que el subproceso que creó un elemento de la IU es el único subproceso que puede actualizar ese elemento de la IU. Esto es un problema, porque su código generará una excepción si intenta actualizar los elementos de la interfaz de usuario a través de un subproceso de grupo de subprocesos. De alguna manera, el subproceso del grupo de subprocesos debe tener el subproceso GUI que actualice los elementos de la interfaz de usuario.
  • Las aplicaciones ASP.NET permiten que cualquier subproceso haga lo que quiera. Cuando un subproceso del grupo de subprocesos comienza a procesar la solicitud de un cliente, puede asumir la cultura del cliente, lo que permite que el servidor web devuelva el formato específico de la cultura para números, fechas y horas. Además, el servidor web puede asumir la identidad del cliente, de modo que el servidor pueda acceder solo a los recursos a los que el cliente tiene permitido acceder. Cuando un subproceso del grupo de subprocesos genera una operación asincrónica, puede ser completado por otro subproceso del grupo de subprocesos, que procesará el resultado de una operación asincrónica. Mientras este trabajo se realiza en nombre de la solicitud original del cliente, la cultura y la identidad deben "fluir" al nuevo subproceso del grupo de subprocesos, de modo que cualquier trabajo adicional realizado en nombre del cliente se realice utilizando la información de cultura e identidad del cliente.

En pocas palabras, a SynchronizationContext -objeto derivado conecta un modelo de aplicación a su modelo de subprocesamiento . La FCL define varias clases derivadas de SynchronizationContext, pero normalmente no tratará directamente con estas clases; de hecho, muchos de ellos no están expuestos ni documentados públicamente.

En su mayor parte, los desarrolladores de aplicaciones no necesitan saber nada sobre el SynchronizationContext clase. Cuando await un Task , el hilo de llamada SynchronizationContext se obtiene el objeto. Cuando un subproceso de grupo de subprocesos completa el Task , el SynchronizationContext se utiliza el objeto, lo que garantiza el modelo de subprocesamiento correcto para su modelo de aplicación. Entonces, cuando un subproceso GUIawaits un Task , el código que sigue al await se garantiza que el operador también se ejecutará en el subproceso de la GUI , lo que permite que ese código actualice los elementos de la interfaz de usuario. Para una aplicación ASP.NET, se garantiza que el código que sigue al operador await se ejecutará en un grupo de subprocesos que tiene la cultura del cliente y la información principal asociada .

Por supuesto, puede definir su propia clase derivada de TaskScheduler si tiene necesidades especiales de programación de tareas. Microsoft ha proporcionado un montón de código de muestra para tareas e incluye el código fuente para un montón de programadores de tareas en el paquete Extras de Parallel Extensions. Me gusta, IOTaskScheduler , LimitedConcurrencyLevelTaskScheduler , OrderedTaskScheduler , PrioritizingTaskScheduler , ThreadPerTaskScheduler .


Cada plataforma tiene su propio "programador" y tienen sus propias abstracciones a su alrededor. p.ej. WinForms utiliza una bomba de mensajes. WPF usa otra bomba de mensajes abstraída dentro de "Dispatcher". Un ThreadPool es otro "planificador" abstraído dentro de "ThreadPool". Estos (y algunos otros) son planificadores de bajo nivel.

A Task y TaskScheduler les gustaría que el usuario de Task no tuviera que pensar en programar tareas en estos niveles inferiores (por supuesto, puede hacerlo de forma abstracta). Debería poder iniciar una tarea y un "programador" ambiental debería encargarse de ello. Por ejemplo, TaskFactory.StartNew(()=>{LengthyOperation()}) debería funcionar independientemente de la plataforma en la que me esté ejecutando. Ahí es donde un SynchronizationContext entra. Sabe qué programadores de nivel inferior están involucrados en el marco que se está ejecutando actualmente. Eso se pasa a un TaskScheduler y ese programador puede programar tareas (posiblemente en ThreadPool) y programar continuaciones a través del programador de nivel inferior asociado con el marco que se está ejecutando actualmente (ver SynchronizationContext ) para mantener los requisitos de sincronización. p.ej. aunque le gustaría que su tarea se ejecute en ThreadPool, es posible que desee que se ejecute una continuación en el subproceso de la interfaz de usuario.

Es importante saber que el TaskScheduler es una abstracción de muchos otros programadores. Esta no es la única razón por la que existe, sino una de las razones de esta abstracción "extra".


Aunque, como se cita,

En mi opinión, el grado de abstracción (y, por lo tanto, API) difiere. SynchronizationContext es una API más genérica en el sentido de que Post/Send toma un delegado de método simple.

Por otro lado, TaskScheduler es una abstracción específica de TPL, por lo que ofrece métodos como QueueTask que se ocupa de Task objeto. El uso del contexto de sincronización en lugar del programador de tareas (es decir, tener una implementación específica de TPL de SynchronizationContext) habría hecho que trabajar con la programación de tareas fuera más tedioso (y, por supuesto, sería una API débilmente tipada en el contexto de TPL). Entonces, los diseñadores de TPL han elegido modelar una API de programación abstracta que tiene sentido para TPL (ese es el propósito de la abstracción de todos modos, ¿no?) - por supuesto, para cerrar la brecha, FCL contiene una clase interna SynchronizationContextTaskScheduler esa es la implementación de TaskScheduler de contenedor sobre SynchronizationContext.

SynchronizationContext se introdujo en .NET 2.0 mientras que TPL se introdujo en .NET 4. Es interesante pensar qué habrían elegido los diseñadores de FCL si la secuencia fuera al revés, es decir, si TPL hubiera existido en la época de .NET 2.0. En mi opinión, se podría haber utilizado TaskScheduler en lugar de SynchrinizationContext modelando los delgates como tareas en una especialización específica.