Comment SynchronizationContext.Current du thread principal peut-il devenir nul dans une application Windows Forms?

J’ai un problème dans mon application: À un moment donné, SynchronizationContext.Current devient nul pour le thread principal. Je n’arrive pas à reproduire le même problème dans un projet isolé. Mon vrai projet est complexe; Il mélange Windows Forms et WPF et appelle les services Web WCF. Autant que je sache, ce sont tous les systèmes qui peuvent interagir avec le SynchronizationContext.

Ceci est le code de mon projet isolé. Ma vraie application fait quelque chose qui ressemble à ça. Cependant, dans ma vraie application, SynchronizationContext.Current est null sur le thread principal lorsque la tâche de continuation est exécutée.

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’est-ce qui pourrait faire en sorte que SynchronizationContext.Current du thread principal devienne nul?

Modifier:

@Hans a demandé la trace de la stack. C’est ici:


    à MyApp.Framework.UI.Commands.AsyncCommand.HandleTaskError (tâche de tâche) dans d: \ sources \ s2 \ Framework \ Sources \ UI \ Commands \ AsyncCommand.cs: ligne 157
    at System.Threading.Tasks.Task.c__DisplayClassb.b__a (Object obj)
    à System.Threading.Tasks.Task.InnerInvoke ()
    à System.Threading.Tasks.Task.Execute ()
    at System.Threading.Tasks.Task.ExecutionContextCallback (Object obj)
    at System.Threading.ExecutionContext.runTryCode (Object userData)
    à System.Runtime.ComstackrServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup (code TryCode, code retour de CleanupCode, object userData)
    at System.Threading.ExecutionContext.RunInternal (ExecutionContext executionContext, rappel de ContextCallback, état de l'object)
    at System.Threading.ExecutionContext.Run (ExecutionContext executionContext, rappel ContextCallback, état de l'object, 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 (méthode IRuntimeMethodInfo, cible de l'object, arguments Object [], SignatureStruct & sig, méthodeAtsortingbutes MethodAtsortingbutes, RuntimeType typeOwner)
    at System.RuntimeMethodHandle.InvokeMethodFast (méthode IRuntimeMethodInfo, cible de l'object, arguments Object [], signature sig, méthodeAtsortingbutesAtsortingbutes, RuntimeType typeOwner)
    at System.Reflection.RuntimeMethodInfo.Invoke (Object obj, BindingFlags invokeAttr, classeur Binder, parameters Object [], culture CultureInfo, contrôles booléens skipVisibilityCheck)
    at System.Delegate.DynamicInvokeImpl (Object [] args)
    à System.Windows.Forms.Control.InvokeMarshaledCallbackDo (ThreadMethodEntry tme)
    at System.Windows.Forms.Control.InvokeMarshaledCallbackHelper (Object obj)
    at System.Threading.ExecutionContext.runTryCode (Object userData)
    à System.Runtime.ComstackrServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup (code TryCode, code retour de CleanupCode, object userData)
    at System.Threading.ExecutionContext.RunInternal (ExecutionContext executionContext, rappel de ContextCallback, état de l'object)
    at System.Threading.ExecutionContext.Run (ExecutionContext executionContext, rappel ContextCallback, état de l'object, Boolean ignoreSyncCtx)
    à System.Threading.ExecutionContext.Run (ExecutionContext executionContext, rappel de ContextCallback, état de l'object)
    à System.Windows.Forms.Control.InvokeMarshaledCallback (ThreadMethodEntry tme)
    à System.Windows.Forms.Control.InvokeMarshaledCallbacks ()
    à System.Windows.Forms.Control.WndProc (Message & m)
    à System.Windows.Forms.Control.ControlNativeWindow.OnMessage (Message & m)
    à System.Windows.Forms.Control.ControlNativeWindow.WndProc (Message & m)
    sur System.Windows.Forms.NativeWindow.Callback (IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
    à System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW (MSG & msg)
    at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop (IntPtr dwComponentID, raison Int32, Int32 pvLoopData)
    at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner (raison Int32, contexte ApplicationContext)
    at System.Windows.Forms.Application.ThreadContext.RunMessageLoop (raison Int32, contexte ApplicationContext)
    à System.Windows.Forms.Application.Run (Form MainForm)
    à MyApp.Framework.SharedUI.ApplicationBase.InternalStart () dans d: \ sources \ s2 \ Framework \ Sources \ UI \ SharedUI \ ApplicationBase.cs: ligne 190
    à MyApp.Framework.SharedUI.ApplicationBase.Start () dans d: \ sources \ s2 \ Framework \ Sources \ UI \ SharedUI \ ApplicationBase.cs: ligne 118
    dans MyApp.App1.WinUI.HDA.Main () dans d: \ sources \ s2 \ App1 \ Sources \ WinUI \ HDA.cs: ligne 63

Sly, j’ai rencontré exactement le même comportement lorsqu’un mélange de WPF, WCF et TPL est utilisé. Le SynchronizationContext du thread principal deviendra nul dans quelques situations.

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

Selon ce post sur les forums msdn, il s’agit d’un bogue confirmé dans la TPL en 4.0. Un collègue s’exécute sur la version 4.5 et ne voit pas ce comportement.

Nous avons résolu ce problème en créant un TaskScheduler dans un singleton statique avec le thread principal utilisant FromCurrentSynchronizationContext, puis en référençant toujours ce planificateur de tâches lors de la création de continuations. Par exemple

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

Cela évite le problème dans la TPL sur .net 4.0.

Mise à jour Si .net 4.5 est installé sur votre ordinateur de développement, vous ne verrez pas ce problème même si vous ciblez le framework 4.0. Vos utilisateurs qui ont uniquement installé 4.0 seront toujours affectés.

Je ne sais pas si c’est la méthode préférée mais voici comment j’utilise le SynchronizationContext:

Dans votre constructeur (thread principal), enregistrez une copie du contexte actuel, de sorte que vous êtes assuré (??) d’avoir le bon contexte plus tard, quel que soit le thread sur lequel vous vous trouvez.

 _uiCtx = SynchronizationContext.Current; 

Et plus tard dans votre tâche, utilisez-le pour interagir avec le thread principal de l’interface utilisateur

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

J’ai créé une classe pour cela. Cela ressemble à ceci:

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

Au démarrage de mon application, j’appelle UIContext.Initialize ()

Et quand j’en ai besoin dans une tâche, je mets juste UIContext.Current en tant que TaskScheduler.

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