.net - wfa - Usar SynchronizationContext para enviar eventos de vuelta a la interfaz de usuario para WinForms o WPF
wpf que es (2)
En lugar de compararlo con el actual, ¿por qué no dejar que se preocupe por eso? entonces es simplemente un caso de manejo del caso "sin contexto":
static void RaiseOnUIThread(EventHandler handler, object sender) {
if (handler != null) {
SynchronizationContext ctx = SynchronizationContext.Current;
if (ctx == null) {
handler(sender, EventArgs.Empty);
} else {
ctx.Post(delegate { handler(sender, EventArgs.Empty); }, null);
}
}
}
Estoy utilizando un SynchronizationContext para ordenar los eventos de nuevo a la secuencia de comandos de la UI de mi archivo DLL que realiza muchas tareas de fondo de subprocesos múltiples.
Sé que el patrón singleton no es un favorito, pero lo estoy usando por ahora para almacenar una referencia del SynchronizationContext de la interfaz de usuario cuando creas el objeto primario de foo.
public class Foo
{
public event EventHandler FooDoDoneEvent;
public void DoFoo()
{
//stuff
OnFooDoDone();
}
private void OnFooDoDone()
{
if (FooDoDoneEvent != null)
{
if (TheUISync.Instance.UISync != SynchronizationContext.Current)
{
TheUISync.Instance.UISync.Post(delegate { OnFooDoDone(); }, null);
}
else
{
FooDoDoneEvent(this, new EventArgs());
}
}
}
}
Esto no funcionó en WPF, la sincronización de la IU de las instancias de TheUISync (que se alimenta desde la ventana principal) nunca coincide con el SynchronizationContext.Current actual. En Windows, cuando hago lo mismo, coincidirán después de una invocación y volveremos al hilo correcto.
Mi solución, que odio, parece
public class Foo
{
public event EventHandler FooDoDoneEvent;
public void DoFoo()
{
//stuff
OnFooDoDone(false);
}
private void OnFooDoDone(bool invoked)
{
if (FooDoDoneEvent != null)
{
if ((TheUISync.Instance.UISync != SynchronizationContext.Current) && (!invoked))
{
TheUISync.Instance.UISync.Post(delegate { OnFooDoDone(true); }, null);
}
else
{
FooDoDoneEvent(this, new EventArgs());
}
}
}
}
Así que espero que esta muestra tenga suficiente sentido para seguir.
El problema inmediato
Su problema inmediato es que SynchronizationContext.Current
no se establece automáticamente para WPF. Para configurarlo, tendrá que hacer algo como esto en su código TheUISync cuando se ejecuta bajo WPF:
var context = new DispatcherSynchronizationContext(
Application.Current.Dispatcher);
SynchronizationContext.SetSynchronizationContext(context);
UISync = context;
Un problema más profundo
SynchronizationContext
está vinculado con el soporte COM + y está diseñado para cruzar hilos. En WPF no puede tener un Dispatcher que abarque varios hilos, por lo que un SynchronizationContext
no puede cruzar los hilos. Hay una serie de escenarios en los que un SynchronizationContext
puede cambiar a un nuevo hilo, específicamente cualquier cosa que llame a ExecutionContext.Run()
. Por lo tanto, si utiliza SynchronizationContext
para proporcionar eventos a los clientes de WinForms y WPF, debe tener en cuenta que algunos escenarios se romperán; por ejemplo, una solicitud web a un servicio web o sitio alojado en el mismo proceso sería un problema.
Cómo moverse necesitando SynchronizationContext
Debido a esto, sugiero usar el mecanismo Dispatcher
de WPF exclusivamente para este fin, incluso con el código de WinForms. Ha creado una clase singleton "TheUISync" que almacena la sincronización, por lo que claramente tiene alguna forma de enganchar en el nivel superior de la aplicación. Independientemente de lo que esté haciendo, puede agregar código que cree agrega algo de contenido WPF a su aplicación WinForms para que Dispatcher
funcione, luego use el nuevo mecanismo Dispatcher
que describo a continuación.
Usar Dispatcher en lugar de SynchronizationContext
El mecanismo Dispatcher
de WPF en realidad elimina la necesidad de un objeto SynchronizationContext
separado. A menos que tenga ciertos escenarios de interoperabilidad tales como el código compartido con objetos COM + o interfaces de usuario de WinForms, su mejor solución es usar Dispatcher
lugar de SynchronizationContext
.
Esto se ve así:
public class Foo
{
public event EventHandler FooDoDoneEvent;
public void DoFoo()
{
//stuff
OnFooDoDone();
}
private void OnFooDoDone()
{
if(FooDoDoneEvent!=null)
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(() =>
{
FooDoDoneEvent(this, new EventArgs());
}));
}
}
Tenga en cuenta que ya no necesita un objeto TheUISync: WPF maneja ese detalle por usted.
Si te sientes más cómodo con la sintaxis de delegate
anterior, puedes hacerlo de esa manera:
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(delegate
{
FooDoDoneEvent(this, new EventArgs());
}));
Un error no relacionado para arreglar
También tenga en cuenta que hay un error en su código original que se replica aquí. El problema es que FooDoneEvent se puede establecer como nulo entre el momento en que se llama OnFooDoDone y el momento en que BeginInvoke
(o Post
en el código original) llama al delegado. La solución es una segunda prueba dentro del delegado:
if(FooDoDoneEvent!=null)
Application.Current.Dispatcher.BeginInvoke(
DispatcherPriority.Normal, new Action(() =>
{
if(FooDoDoneEvent!=null)
FooDoDoneEvent(this, new EventArgs());
}));