En el libro Programming C #, tiene un código de muestra sobre SynchronizationContext
:
SynchronizationContext originalContext = SynchronizationContext.Current;
ThreadPool.QueueUserWorkItem(delegate {
string text = File.ReadAllText(@"c:\temp\log.txt");
originalContext.Post(delegate {
myTextBox.Text = text;
}, null);
});
Soy un principiante en hilos, así que por favor conteste en detalle. Primero, no sé qué significa el contexto, ¿qué guarda el programa en el originalContext
? Y cuando Post
se activa el método, ¿qué hará el hilo de la interfaz de usuario?
Si pregunto algunas tonterías, corrígeme, ¡gracias!
EDITAR: Por ejemplo, ¿qué pasa si solo escribo myTextBox.Text = text;
en el método, cuál es la diferencia?
c#
.net
multithreading
nublado
fuente
fuente
async
/await
depende deSynchronizationContext
debajo.Respuestas:
En pocas palabras,
SynchronizationContext
representa una ubicación "donde" podría ejecutarse el código. Los delegados que se pasan a suSend
oPost
método serán invocados en esa ubicación. (Post
es la versión no bloqueante / asíncrona deSend
).Cada hilo puede tener una
SynchronizationContext
instancia asociada a él. El subproceso en ejecución se puede asociar con un contexto de sincronización llamando al método estáticoSynchronizationContext.SetSynchronizationContext
, y el contexto actual del subproceso en ejecución se puede consultar a través de laSynchronizationContext.Current
propiedad .A pesar de lo que acabo de escribir (cada hilo tiene un contexto de sincronización asociado), a
SynchronizationContext
no representa necesariamente un hilo específico ; también puede reenviar la invocación de los delegados que se le pasaron a cualquiera de varios subprocesos (por ejemplo, a unThreadPool
subproceso de trabajo), o (al menos en teoría) a un núcleo de CPU específico , o incluso a otro host de red . El lugar donde sus delegados terminan funcionando depende del tipo deSynchronizationContext
utilizado.Windows Forms instalará un
WindowsFormsSynchronizationContext
en el hilo en el que se crea el primer formulario. (Este hilo se denomina comúnmente "el hilo de la interfaz de usuario"). Este tipo de contexto de sincronización invoca a los delegados que se le pasan exactamente en ese hilo. Esto es muy útil ya que Windows Forms, como muchos otros marcos de interfaz de usuario, solo permite la manipulación de controles en el mismo subproceso en el que se crearon.El código al que ha pasado
ThreadPool.QueueUserWorkItem
se ejecutará en un subproceso de trabajo de grupo de subprocesos. Es decir, no se ejecutará en el subproceso en el quemyTextBox
se creó, por lo que Windows Forms tarde o temprano (especialmente en las versiones de lanzamiento) arrojará una excepción, diciéndole que no puede accedermyTextBox
desde otro subproceso.Esta es la razón por la que tiene que "cambiar" de alguna manera el subproceso de trabajo al "subproceso de interfaz de usuario" (donde
myTextBox
se creó) antes de esa asignación en particular. Esto se hace de la siguiente manera:Mientras todavía está en el hilo de la interfaz de usuario, capture los formularios de Windows
SynchronizationContext
allí y almacene una referencia a él en una variable (originalContext
) para su uso posterior. Debe consultarSynchronizationContext.Current
en este punto; si lo consulta dentro del código que se le pasaThreadPool.QueueUserWorkItem
, puede obtener el contexto de sincronización asociado con el subproceso de trabajo del grupo de subprocesos. Una vez que haya almacenado una referencia al contexto de los formularios de Windows, puede usarlo en cualquier lugar y en cualquier momento para "enviar" código al hilo de la interfaz de usuario.Siempre que necesite manipular un elemento de la interfaz de usuario (pero ya no esté, o podría no estar, en el hilo de la interfaz de usuario), acceda al contexto de sincronización de Windows Forms a través de
originalContext
y entregue el código que manipulará la interfaz de usuario a cualquieraSend
oPost
.Observaciones finales y sugerencias:
Lo que los contextos de sincronización no harán por usted es decirle qué código debe ejecutarse en una ubicación / contexto específico, y qué código puede ejecutarse normalmente, sin pasarlo a a
SynchronizationContext
. Para decidir eso, debe conocer las reglas y requisitos del marco contra el que está programando: Windows Forms en este caso.Por lo tanto, recuerde esta regla simple para Windows Forms: NO acceda a controles o formularios desde un hilo que no sea el que los creó. Si debe hacer esto, use el
SynchronizationContext
mecanismo como se describe anteriormente, oControl.BeginInvoke
(que es una forma específica de Windows Forms de hacer exactamente lo mismo).Si está programando contra .NET 4.5 o posterior, puede hacer su vida mucho más fácil mediante la conversión de su código que explícitamente usos
SynchronizationContext
,ThreadPool.QueueUserWorkItem
,control.BeginInvoke
, etc a los nuevosasync
/await
palabras clave y la tarea paralela Biblioteca (TPL) , es decir, que rodea la API elTask
y lasTask<TResult>
clases. Estos, en un grado muy alto, se encargarán de capturar el contexto de sincronización del subproceso de la interfaz de usuario, comenzar una operación asincrónica y luego volver al subproceso de la interfaz de usuario para que pueda procesar el resultado de la operación.fuente
Application.Run
IIRC). Este es un tema bastante avanzado y no algo hecho casualmente.null
) o una instancia deSynchronizationContext
(o una subclase de él). El punto de esa cita no era lo que obtienes, sino lo que no obtendrás: el contexto de sincronización del hilo de la interfaz de usuario.Me gustaría agregar a otras respuestas,
SynchronizationContext.Post
solo pone en cola una devolución de llamada para su posterior ejecución en el hilo objetivo (normalmente durante el siguiente ciclo del bucle de mensajes del hilo objetivo), y luego la ejecución continúa en el hilo de llamada. Por otro lado,SynchronizationContext.Send
intenta ejecutar la devolución de llamada en el subproceso de destino inmediatamente, lo que bloquea el subproceso de llamada y puede provocar un punto muerto. En ambos casos, existe la posibilidad de reentrada de código (ingresar un método de clase en el mismo hilo de ejecución antes de que la llamada anterior al mismo método haya regresado).Si está familiarizado con el modelo de programación Win32, una muy estrecha analogía sería
PostMessage
ySendMessage
API, que se puede llamar para enviar un mensaje de un hilo diferente de uno de la ventana de destino.Aquí hay una muy buena explicación de lo que son los contextos de sincronización: se trata del contexto de sincronización .
fuente
Almacena el proveedor de sincronización, una clase derivada de SynchronizationContext. En este caso, probablemente será una instancia de WindowsFormsSynchronizationContext. Esa clase usa los métodos Control.Invoke () y Control.BeginInvoke () para implementar los métodos Send () y Post (). O puede ser DispatcherSynchronizationContext, usa Dispatcher.Invoke () y BeginInvoke (). En una aplicación Winforms o WPF, ese proveedor se instala automáticamente tan pronto como crea una ventana.
Cuando ejecuta código en otro subproceso, como el subproceso de grupo de subprocesos utilizado en el fragmento, debe tener cuidado de no utilizar directamente objetos que no sean seguros para subprocesos. Al igual que cualquier objeto de interfaz de usuario, debe actualizar la propiedad TextBox.Text del hilo que creó el TextBox. El método Post () asegura que el objetivo delegado se ejecute en ese hilo.
Tenga en cuenta que este fragmento es un poco peligroso, solo funcionará correctamente cuando lo llame desde el hilo de la interfaz de usuario. SynchronizationContext.Current tiene diferentes valores en diferentes hilos. Solo el hilo de la interfaz de usuario tiene un valor utilizable. Y es la razón por la que el código tuvo que copiarlo. Una forma más legible y segura de hacerlo, en una aplicación Winforms:
Lo cual tiene la ventaja de que funciona cuando se llama desde cualquier hilo. La ventaja de usar SynchronizationContext.Current es que aún funciona si el código se usa en Winforms o WPF, es importante en una biblioteca. Ciertamente, este no es un buen ejemplo de dicho código, siempre sabes qué tipo de TextBox tienes aquí, así que siempre sabes si usar Control.BeginInvoke o Dispatcher.BeginInvoke. En realidad, usar SynchronizationContext.Current no es tan común.
El libro está tratando de enseñarte sobre el enhebrado, por lo que está bien usar este ejemplo defectuoso. En la vida real, en los pocos casos en los que podría considerar usar SynchronizationContext.Current, aún lo dejaría a las palabras clave asíncronas / en espera de C # o TaskScheduler.FromCurrentSynchronizationContext () para que lo haga por usted. Pero tenga en cuenta que todavía se comportan mal como lo hace el fragmento cuando los usa en el hilo incorrecto, exactamente por la misma razón. Una pregunta muy común por aquí, el nivel adicional de abstracción es útil pero hace que sea más difícil descubrir por qué no funcionan correctamente. Esperemos que el libro también te diga cuándo no usarlo :)
fuente
El propósito del contexto de sincronización aquí es asegurarse de que
myTextbox.Text = text;
se llame al hilo principal de la interfaz de usuario.Windows requiere que los controles de la GUI sean accedidos solo por el hilo con el que fueron creados. Si intenta asignar el texto en un hilo de fondo sin sincronizar primero (por cualquiera de varios medios, como este o el patrón Invocar), se generará una excepción.
Lo que esto hace es guardar el contexto de sincronización antes de crear el subproceso de fondo, luego el subproceso de fondo usa el contexto. El método Post ejecuta el código GUI.
Sí, el código que has mostrado es básicamente inútil. ¿Por qué crear un subproceso de fondo, solo para volver inmediatamente al subproceso principal de la interfaz de usuario? Es solo un ejemplo.
fuente
A la fuente
Por ejemplo: supongamos que tiene dos hilos, Thread1 y Thread2. Digamos, Thread1 está haciendo algo de trabajo, y luego Thread1 desea ejecutar el código en Thread2. Una forma posible de hacerlo es pedirle a Thread2 su objeto SynchronizationContext, dárselo a Thread1, y luego Thread1 puede llamar a SynchronizationContext.Send para ejecutar el código en Thread2.
fuente
SynchronizationContext nos proporciona una forma de actualizar una IU desde un hilo diferente (sincrónicamente a través del método Send o asincrónicamente a través del método Post).
Eche un vistazo al siguiente ejemplo:
SynchronizationContext.Current devolverá el contexto de sincronización del hilo de la interfaz de usuario. ¿Cómo se esto? Al comienzo de cada formulario o aplicación WPF, el contexto se establecerá en el hilo de la interfaz de usuario. Si crea una aplicación WPF y ejecuta mi ejemplo, verá que cuando hace clic en el botón, duerme aproximadamente 1 segundo, luego muestra el contenido del archivo. Es posible que espere que no lo haga porque la persona que llama del método UpdateTextBox (que es Work1) es un método pasado a un subproceso, por lo tanto, debe suspender ese subproceso, no el subproceso principal de la interfaz de usuario, ¡NO! Aunque el método Work1 se pasa a un subproceso, tenga en cuenta que también acepta un objeto que es el SyncContext. Si lo mira, verá que el método UpdateTextBox se ejecuta a través del método syncContext.Post y no el método Work1. Echa un vistazo a lo siguiente:
El último ejemplo y este se ejecuta igual. Ambos no bloquean la interfaz de usuario mientras hace trabajos.
En conclusión, piense en SynchronizationContext como un hilo. No es un hilo, define un hilo (tenga en cuenta que no todos los hilos tienen un SyncContext). Cada vez que llamamos al método Post o Send para actualizar una interfaz de usuario, es como actualizar la interfaz de usuario normalmente desde el hilo de la interfaz de usuario principal. Si, por alguna razón, necesita actualizar la interfaz de usuario desde un subproceso diferente, asegúrese de que ese subproceso tenga el SyncContext del subproceso de la interfaz de usuario principal y simplemente llame al método Enviar o Publicar con el método que desea ejecutar y ya está conjunto.
Espero que esto te ayude, amigo!
fuente
SynchronizationContext básicamente es un proveedor de ejecución de delegados de devolución de llamada, principalmente responsable de garantizar que los delegados se ejecuten en un contexto de ejecución dado después de que una parte particular del código (encapsulado en un obj de tarea de .Net TPL) de un programa haya completado su ejecución.
Desde el punto de vista técnico, SC es una clase simple de C # que está orientada a admitir y proporcionar su función específicamente para los objetos de la Biblioteca Paralela de Tareas.
Cada aplicación .Net, excepto las aplicaciones de consola, tiene una implementación particular de esta clase basada en el marco subyacente específico, es decir: WPF, WindowsForm, Asp Net, Silverlight, etc.
La importancia de este objeto está ligada a la sincronización entre los resultados que regresan de la ejecución asíncrona de código y la ejecución de código dependiente que espera los resultados de ese trabajo asincrónico.
Y la palabra "contexto" significa contexto de ejecución, que es el contexto de ejecución actual donde se ejecutará ese código de espera, es decir, la sincronización entre el código asíncrono y su código de espera ocurre en un contexto de ejecución específico, por lo que este objeto se llama SynchronizationContext: representa el contexto de ejecución que se ocupará de la sincronización del código asíncrono y la ejecución del código de espera .
fuente
Este ejemplo es de ejemplos de Linqpad de Joseph Albahari, pero realmente ayuda a comprender lo que hace el contexto de sincronización.
fuente