¿Cómo puede SynchronizationContext.Current del hilo principal convertirse en nulo en una aplicación de Windows Forms?

Tengo un problema en mi aplicación: en algún momento, SynchronizationContext.Current pasa a ser nulo para el hilo principal. No puedo reproducir el mismo problema en un proyecto aislado. Mi verdadero proyecto es complejo; mezcla formularios de Windows y WPF y llama a los servicios web de WCF. Hasta donde yo sé, esos son todos los sistemas que pueden interactuar con SynchronizationContext.

Este es el código de mi proyecto aislado. Mi aplicación real hace algo que se parece a eso. Sin embargo, en mi aplicación real, SynchronizationContext.Current es nulo en el hilo principal cuando se ejecuta la tarea de continuación.

private void button2_Click(object sender, EventArgs e) { if (SynchronizationContext.Current == null) { Debug.Fail("SynchronizationContext.Current is null"); } Task.Factory.StartNew(() => { CallWCFWebServiceThatThrowsAnException(); }) .ContinueWith((t) => { //update the UI UpdateGUI(t.Exception); if (SynchronizationContext.Current == null) { Debug.Fail("SynchronizationContext.Current is null"); } }, CancellationToken.None, TaskContinuationOptions.OnlyOnFaulted, TaskScheduler.FromCurrentSynchronizationContext()); } 

¿Qué podría hacer que SynchronizationContext.Current del hilo principal se vuelva nulo?

Editar:

@Hans pidió el seguimiento de la stack. Aquí está:


    en MyApp.Framework.UI.Commands.AsyncCommand.HandleTaskError (tarea Tarea) en d: \ sources \ s2 \ Framework \ Sources \ UI \ Commands \ AsyncCommand.cs: línea 157
    at System.Threading.Tasks.Task.c__DisplayClassb.b__a (Object obj)
    en System.Threading.Tasks.Task.InnerInvoke ()
    en System.Threading.Tasks.Task.Execute ()
    at System.Threading.Tasks.Task.ExecutionContextCallback (Object obj)
    en System.Threading.ExecutionContext.runTryCode (Object userData)
    en System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup (Código TryCode, CleanupCode backoutCode, Object userData)
    en System.Threading.ExecutionContext.RunInternal (ExecutionContext executionContext, ContextCallback callback, estado Object)
    en System.Threading.ExecutionContext.Run (ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
    en System.Threading.Tasks.Task.ExecuteWithThreadLocal (Task & currentTaskSlot)
    en System.Threading.Tasks.Task.ExecuteEntry (Boolean bPreventDoubleExecution)
    en System.Threading.Tasks.SynchronizationContextTaskScheduler.PostCallback (Object obj)
    en System.RuntimeMethodHandle._InvokeMethodFast (método IRuntimeMethodInfo, Object object, Object [] arguments, SignatureStruct & sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
    en System.RuntimeMethodHandle.InvokeMethodFast (método IRuntimeMethodInfo, Object object, Object [] arguments, Signature sig, MethodAttributes methodAttributes, RuntimeType typeOwner)
    en System.Reflection.RuntimeMethodInfo.Invoke (Object obj, BindingFlags invokeAttr, Binder Binder, Object [] parámetros, CultureInfo culture, Boolean skipVisibilityChecks)
    en System.Delegate.DynamicInvokeImpl (Object [] args)
    en System.Windows.Forms.Control.InvokeMarshaledCallbackDo (ThreadMethodEntry tme)
    en System.Windows.Forms.Control.InvokeMarshaledCallbackHelper (Object obj)
    en System.Threading.ExecutionContext.runTryCode (Object userData)
    en System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup (Código TryCode, CleanupCode backoutCode, Object userData)
    en System.Threading.ExecutionContext.RunInternal (ExecutionContext executionContext, ContextCallback callback, estado Object)
    en System.Threading.ExecutionContext.Run (ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
    en System.Threading.ExecutionContext.Run (ExecutionContext executionContext, callback ContextCallback, estado Object)
    en System.Windows.Forms.Control.InvokeMarshaledCallback (ThreadMethodEntry tme)
    en System.Windows.Forms.Control.InvokeMarshaledCallbacks ()
    en System.Windows.Forms.Control.WndProc (Message & m)
    en System.Windows.Forms.Control.ControlNativeWindow.OnMessage (Message & m)
    en System.Windows.Forms.Control.ControlNativeWindow.WndProc (Message & m)
    en System.Windows.Forms.NativeWindow.Callback (IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
    en System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW (MSG y msg)
    en System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop (IntPtr dwComponentID, Int32 reason, Int32 pvLoopData)
    en System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner (Int32 reason, ApplicationContext context)
    en System.Windows.Forms.Application.ThreadContext.RunMessageLoop (razón Int32, contexto de ApplicationContext)
    en System.Windows.Forms.Application.Run (Form mainForm)
    en MyApp.Framework.SharedUI.ApplicationBase.InternalStart () en d: \ sources \ s2 \ Framework \ Sources \ UI \ SharedUI \ ApplicationBase.cs: línea 190
    en MyApp.Framework.SharedUI.ApplicationBase.Start () en d: \ sources \ s2 \ Framework \ Sources \ UI \ SharedUI \ ApplicationBase.cs: línea 118
    en MyApp.App1.WinUI.HDA.Main () en d: \ sources \ s2 \ App1 \ Sources \ WinUI \ HDA.cs: línea 63

Sly, he encontrado exactamente el mismo comportamiento cuando se usa una mezcla de WPF, WCF y TPL. El SynchronizationContext actual del subproceso principal será nulo en algunas situaciones.

 var context = SynchronizationContext.Current; // if context is null, an exception of // The current SynchronizationContext may not be used as a TaskScheduler. // will be thrown TaskScheduler.FromCurrentSynchronizationContext(); 

De acuerdo con esta publicación en los foros msdn, este es un error confirmado en el TPL en 4.0. Un compañero de trabajo se ejecuta en 4.5 y no ve este comportamiento.

Solucionamos esto creando un TaskScheduler en un singleton estático con el hilo principal usando FromCurrentSynchronizationContext y luego siempre hacemos referencia al progtwigdor de tareas al crear continuaciones. Por ejemplo

 Task task = Task.Factory.StartNew(() => { // something } ).ContinueWith(t => { // ui stuff }, TheSingleton.Current.UiTaskScheduler); 

Esto evita el problema en el TPL en .net 4.0.

Actualización Si tiene .net 4.5 instalado en su máquina de desarrollo, no verá este problema, incluso si está apuntando al framework 4.0. Sus usuarios que solo tienen 4.0 instalado todavía se verán afectados.

No estoy seguro si este es el método preferido, pero aquí es cómo uso el SynchronizationContext:

En su constructor (hilo principal), guarde una copia del contexto actual, de esa manera se le garantiza (??) que tendrá el contexto correcto más adelante, sin importar en qué hilo se encuentre.

 _uiCtx = SynchronizationContext.Current; 

Y más tarde en tu tarea, úsala para interactuar con el hilo principal de la interfaz de usuario

 _uiCtx.Post( ( o ) => { //UI Stuff goes here }, null ); 

Creé una clase para esto. Se parece a esto:

 public class UIContext { private static TaskScheduler m_Current; public static TaskScheduler Current { get { return m_Current; } private set { m_Current = value; } } public static void Initialize() { if (Current != null) return; if (SynchronizationContext.Current == null) SynchronizationContext.SetSynchronizationContext(new SynchronizationContext()); Current = TaskScheduler.FromCurrentSynchronizationContext(); } } 

Al iniciar mi aplicación, llamo a UIContext.Initialize ()

Y cuando lo necesito en una tarea, simplemente pongo UIContext.Current como TaskScheduler.

 Task.Factory.StartNew(() => { //Your code here }, CancellationToken.None, TaskCreationOptions.None, UIContext.Current);