40 votes

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

J'ai un problème dans mon application: À un certain point, la SynchronizationContext.Le courant devient nul pour le thread principal. Je suis incapable de reproduire le même problème dans un cas isolé projet. Mon projet est complexe; il mêle les Windows Forms et WPF et les appels de Services Web WCF. Autant que je sache, ce sont tous les systèmes qui peuvent interagir avec le SynchronizationContext.

C'est le code de mon isolé projet. Ma vraie application fait quelque chose qui ressemble à ça. Cependant, dans mon application réelle de la SynchronizationContext.Le courant est nulle sur le thread principal lorsque la poursuite de la tâche 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());
}

Quelle est la cause de la SynchronizationContext.Courant de le thread principal pour devenir nulle?

Edit:

@Hans demandé la trace de la pile. Ici, il est:


 au MyApp.Cadre.L'INTERFACE utilisateur.Commandes.AsyncCommand.HandleTaskError(Tâche) dans d:\sources\s2\Framework\Sources\UI\Commands\AsyncCommand.cs:line 157
 au Système.Le filetage.Les tâches.De la tâche.c__DisplayClassb.b__un(Object obj)
 au Système.Le filetage.Les tâches.De la tâche.InnerInvoke()
 au Système.Le filetage.Les tâches.De la tâche.Execute()
 au Système.Le filetage.Les tâches.De la tâche.ExecutionContextCallback(Object obj)
 au Système.Le filetage.ExecutionContext.runTryCode(Objet userData)
 au Système.Moment de l'exécution.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Objet userData)
 au Système.Le filetage.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback rappel, état de l'Objet)
 au Système.Le filetage.ExecutionContext.Exécuter(ExecutionContext executionContext, ContextCallback rappel, état de l'Objet, Boolean ignoreSyncCtx)
 au Système.Le filetage.Les tâches.De la tâche.ExecuteWithThreadLocal(Tâche& currentTaskSlot)
 au Système.Le filetage.Les tâches.De la tâche.ExecuteEntry(Boolean bPreventDoubleExecution)
 au Système.Le filetage.Les tâches.SynchronizationContextTaskScheduler.PostCallback(Object obj)
 au Système.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo méthode, l'Objet cible, Object[] arguments, SignatureStruct& sig, les attributs MethodAttributes méthode, RuntimeType typeOwner)
 au Système.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo méthode, l'Objet cible, Object[] arguments, Signature sig, les attributs MethodAttributes méthode, RuntimeType typeOwner)
 au Système.De la réflexion.RuntimeMethodInfo.Invoke(Object obj, BindingFlags invokeAttr, Binder Binder, Object[] paramètres, CultureInfo culture, skipVisibilityChecks Booléens)
 au Système.Délégué.DynamicInvokeImpl(Object[] args)
 au Système.De Windows.Les formulaires.De contrôle.InvokeMarshaledCallbackDo(ThreadMethodEntry tme)
 au Système.De Windows.Les formulaires.De contrôle.InvokeMarshaledCallbackHelper(Object obj)
 au Système.Le filetage.ExecutionContext.runTryCode(Objet userData)
 au Système.Moment de l'exécution.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Objet userData)
 au Système.Le filetage.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback rappel, état de l'Objet)
 au Système.Le filetage.ExecutionContext.Exécuter(ExecutionContext executionContext, ContextCallback rappel, état de l'Objet, Boolean ignoreSyncCtx)
 au Système.Le filetage.ExecutionContext.Exécuter(ExecutionContext executionContext, ContextCallback rappel, état de l'Objet)
 au Système.De Windows.Les formulaires.De contrôle.InvokeMarshaledCallback(ThreadMethodEntry tme)
 au Système.De Windows.Les formulaires.De contrôle.InvokeMarshaledCallbacks()
 au Système.De Windows.Les formulaires.De contrôle.WndProc(Message& m)
 au Système.De Windows.Les formulaires.De contrôle.ControlNativeWindow.OnMessage(Message& m)
 au Système.De Windows.Les formulaires.De contrôle.ControlNativeWindow.WndProc(Message& m)
 au Système.De Windows.Les formulaires.NativeWindow.Rappel(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
 au Système.De Windows.Les formulaires.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
 au System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID, Int32 raison, Int32 pvLoopData)
 au Système.De Windows.Les formulaires.Application.ThreadContext.RunMessageLoopInner(Int32 raison, ApplicationContext contexte)
 au Système.De Windows.Les formulaires.Application.ThreadContext.RunMessageLoop(Int32 raison, ApplicationContext contexte)
 au Système.De Windows.Les formulaires.Application.Exécuter(Formulaire mainForm)
 au MyApp.Cadre.SharedUI.ApplicationBase.InternalStart() dans d:\sources\s2\Framework\Sources\UI\SharedUI\ApplicationBase.cs:line 190
 au MyApp.Cadre.SharedUI.ApplicationBase.Start() dans d:\sources\s2\Framework\Sources\UI\SharedUI\ApplicationBase.cs:line 118
 au MyApp.App1.WinUI.HDA.Main() dans d:\sources\s2\App1\Sources\WinUI\HDA.cs:line 63

43voto

Dan Points 569

Sly, j'ai eu exactement le même comportement quand un mélange de WPF, WCF, et TPL est utilisé. Le thread Principal actuel de SynchronizationContext devient nulle 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 bug dans le TPL dans la version 4.0. Un collègue est en cours d'exécution sur 4,5 et ne voit pas ce comportement.

Nous avons résolu ce problème en créant un TaskScheduler dans un static singleton avec le thread principal à l'aide de FromCurrentSynchronizationContext et puis toujours de référence que le 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 en avez .net 4.5 est 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 4.0 est installé ne seront pas affectés.

10voto

kbeal2k Points 320

Pas sûr que ce soit 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. Ainsi, vous êtes assuré (??) d'avoir le bon contexte ultérieurement, 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 fil principal de l'interface utilisateur.

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

7voto

Steven S. Points 199

J'ai créé un cours 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 viens de mettre UIContext.Current en tant que TaskScheduler.

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X