web-dev-qa-db-fra.com

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

J'ai un problème dans mon application: à un moment donné, le SynchronizationContext.Current devient nul pour le thread principal. Je ne parviens 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 des 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 nul 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 entraîner la nullité du SynchronizationContext.Current du thread principal?

Modifier:

@Hans a demandé la trace de la pile. C'est ici:

 
 sur MyApp.Framework.UI.Commands.AsyncCommand.HandleTaskError (tâche) dans d:\sources\s2\Framework\Sources\UI\Commands\AsyncCommand.cs: ligne 157 
 sur System.Threading.Tasks.Task.c__DisplayClassb.b__a (Object obj) 
 sur System.Threading.Tasks.Task.InnerInvoke () 
 sur System.Threading.Tasks.Task. Execute () 
 Sur System.Threading.Tasks.Task.ExecutionContextCallback (Object obj) 
 Sur System.Threading.ExecutionContext.runTryCode (Object userData) 
 Sur System.Runtime.CompilerServices .RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup (code TryCode, CleanupCode backoutCode, Object userData) 
 Sur System.Threading.ExecutionContext.RunInternal (ExecutionContext executionContext, ContextCallback callback, État de l'objet) 
 Sur System.Threading.Execution (ExecutionContext executionContext, rappel ContextCallback, état de l'objet, Boolean ignoreSyncCtx) 
 Sur System.Threading.Tasks.Task.ExecuteWithThreadLocal (Task & cu rrentTaskSlot) 
 sur System.Threading.Tasks.Task.ExecuteEntry (Boolean bPreventDoubleExecution) 
 sur System.Threading.Tasks.SynchronizationContextTaskScheduler.PostCallback (Object obj) 
 sur System.RuntimeMethod. _InvokeMethodFast (méthode IRuntimeMethodInfo, Object target, Object [] arguments, SignatureStruct & sig, MethodAttributes methodAttributes, RuntimeType typeOwner) 
 At System.RuntimeMethodHandle.InvokeMethodFast (IRuntimeMethodInfo method, ObjectAttribute Object, ObjectAttribute Objects , RuntimeType typeOwner) 
 Sur System.Reflection.RuntimeMethodInfo.Invoke (Object obj, BindingFlags invokeAttr, Binder binder, Object [] parameters, CultureInfo culture, Boolean skipVisibilityChecks) 
 Sur System.Delegate.DynamicInvokeImpl Object [] args) 
 At System.Windows.Forms.Control.InvokeMarshaledCallbackDo (ThreadMethodEntry tme) 
 At System.Windows.Forms.Control.InvokeMarshaledCallbackHelper (Obje ct obj) 
 sur System.Threading.ExecutionContext.runTryCode (Object userData) 
 sur System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup (code TryCode, CleanupCode backoutCode, Object userData) [.____. sur System.Threading.ExecutionContext.RunInternal (ExecutionContext executionContext, rappel ContextCallback, état d'objet) 
 sur System.Threading.ExecutionContext.Run (rappel ExecutionContext executionContext, ContextCallback, état d'objet, booléen ignoreSyncCtx) 
 System.Threading.ExecutionContext.Run (ExecutionContext executionContext, rappel ContextCallback, état de l'objet) 
 Sur System.Windows.Forms.Control.InvokeMarshaledCallback (ThreadMethodEntry tme) 
 Sur System.Windows.Forms.Control. InvokeMarshaledCallbacks () 
 Sur System.Windows.Forms.Control.WndProc (Message & m) 
 Sur System.Windows.Forms.Control.ControlNativeWindow.OnMessage (Message & m) 
 Sur System .Windows.Forms.Control.ControlNative Window.WndProc (Message & m) 
 Sur System.Windows.Forms.NativeWindow.Callback (IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam) 
 Sur System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW (MSG & msg) 
 Sur System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop (IntPtr dwComponentIDID, Int32 reason, Int32 pvLoopData) 
 Sur System.Wind .Forms.Application.ThreadContext.RunMessageLoopInner (motif Int32, contexte ApplicationContext) 
 Sur System.Windows.Forms.Application.ThreadContext.RunMessageLoop (motif Int32, contexte ApplicationContext) 
 Sur System.Windows.Forms .Application.Run (Form mainForm) 
 Sur MyApp.Framework.SharedUI.ApplicationBase.InternalStart () dans d:\sources\s2\Framework\Sources\UI\SharedUI\ApplicationBase.cs: ligne 190 
 sur MyApp.Framework.SharedUI.ApplicationBase.Start () dans d:\sources\s2\Framework\Sources\UI\SharedUI\ApplicationBase.cs: ligne 118 
 sur MyApp.App1.WinUI.HDA.Main () dans d:\sources\s2\App1\Sources\WinUI\HDA.cs: ligne 63 
 
40
Sylvain

Sly, j'ai rencontré exactement le même comportement lorsqu'un mélange de WPF, WCF et TPL est utilisé. Le SynchronizationContext actuel 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 cet article sur les forums msdn, il s'agit d'un bogue confirmé dans le TPL en 4.0. Un collègue s'exécute sur 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 à l'aide de FromCurrentSynchronizationContext, puis référençons 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 le TPL sur .net 4.0.

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

43
Dan

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 cette façon, 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 d'interface utilisateur principal

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

J'ai créé une classe pour ça. 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 lorsque j'en ai besoin dans une tâche, je mets simplement UIContext.Current en tant que TaskScheduler.

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