2011-01-11 17 views
40

Tengo un problema en mi aplicación: en algún punto, SynchronizationContext.Current se vuelve 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.¿Cómo puede convertirse SynchronizationContext.Current del hilo principal en una aplicación de Windows Forms?

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 el SynchronizationContext.Current del hilo principal se vuelva nulo?

Editar:

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

 

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

+0

Establezca un punto de interrupción en UpdateGUI y publique el seguimiento de la pila. –

+0

@Hans: UpdateGUI() está en mi proyecto de muestra. En mi proyecto real, el método se llama HandleTaskError(). He publicado el seguimiento de la pila en mi pregunta. – Sylvain

+2

Parece completamente normal. No tengo ninguna explicación de por qué TaskScheduler.FromCurrentSynchronizationContext() no funciona. Ese es el que proporciona el SC para la llamada Control.InvokeMarshaledCallback(). Ya verifica por un nulo. Suponiendo que está utilizando .NET 4.0 –

Respuesta

9

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

En ti constructor (hilo principal) guardar una copia del contexto actual, de esa manera se están garantizados (??) para tener el contexto correcto más adelante, sin importar el hilo en el que se encuentre.

_uiCtx = SynchronizationContext.Current; 

Y más adelante en su tarea de utilizarlo para hacer interactuar con el hilo principal interfaz de usuario

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

Esto funcionaría con seguridad, pero sería una solución. Me gustaría descubrir cómo el hilo principal perdió su SynchronizationContext. – Sylvain

43

Sly, se han topado con el mismo comportamiento exacto en que una mezcla de WPF, WCF y TPL es usado. 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(); 

Según this post en los foros de MSDN, esto 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.

Resolvimos esto creando un TaskScheduler en un singleton estático con el hilo principal utilizando FromCurrentSynchronizationContext y luego siempre hacemos referencia al programador 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 el equipo de desarrollo, que no verá este problema incluso si usted está apuntando el marco 4.0. Sus usuarios que solo tienen 4.0 instalado todavía se verán afectados.

+1

Experimenté este error: publiqué un programa corto de Winforms que demuestra una manera fácil de reproducir el problema. http://stackoverflow.com/questions/11621372/synchronizationcontext-current-is-null-in-continuation-on-the-main-ui-thread –

+1

con la orientación 4.5.2 aquí, todavía aparece este mensaje de error. No en todas las tareas, sin embargo ... – CularBytes

7

He creado una clase para esto. Se ve así:

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(); 
    } 
} 

En el inicio de mi aplicación Me llaman UIContext.Initialize()

Y cuando lo necesito en una tarea que sólo hay que poner UIContext.Current como TaskScheduler.

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

Funcionó muy bien en un proyecto de Unity (5.5) que estaba teniendo este problema exacto. ¡Gracias! – jeromeyers

Cuestiones relacionadas