c# wpf winforms task-parallel-library

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



wpf winforms (3)

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 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


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


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


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 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 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.