In che modo SynchronizationContext.Current del thread principale diventa null in un’applicazione Windows Forms?

Ho un problema nella mia applicazione: ad un certo punto, SynchronizationContext.Current diventa nullo per il thread principale. Non riesco a riprodurre lo stesso problema in un progetto isolato. Il mio vero progetto è complesso; mescola Windows Form e WPF e chiama servizi Web WCF. Per quanto ne so, questi sono tutti i sistemi che possono interagire con SynchronizationContext.

Questo è il codice del mio progetto isolato. La mia vera app fa qualcosa che assomiglia a questo. Tuttavia, nella mia app reale, SynchronizationContext.Current è nullo sul thread principale quando viene eseguita l’attività di continuazione.

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

Cosa potrebbe causare il fatto che SynchronizationContext.Current del thread principale diventi nullo?

Modificare:

@Hans ha chiesto la traccia dello stack. Ecco qui:

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

Sly, ho incontrato esattamente lo stesso comportamento quando si usa una miscela di WPF, WCF e TPL. Il SynchronizationContext corrente del thread principale diventerà nullo in alcune situazioni.

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

Secondo questo post sui forum msdn, questo è un bug confermato nel TPL in 4.0. Un collaboratore è in esecuzione su 4.5 e non vede questo comportamento.

Abbiamo risolto questo problema creando un TaskScheduler in un singleton statico con il thread principale utilizzando FromCurrentSynchronizationContext e quindi facendo sempre riferimento a quell’utilità di pianificazione quando si creano continuazioni. Per esempio

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

Ciò evita il problema nel TPL su .net 4.0.

Aggiornamento Se hai installato .net 4.5 sul tuo computer di sviluppo, non vedrai questo problema anche se stai mirando al framework 4.0. I tuoi utenti che hanno solo 4.0 installati saranno comunque interessati.

Non sono sicuro se questo è il metodo preferito ma ecco come utilizzo SynchronizationContext:

Nel tuo costruttore (thread principale) salva una copia del contesto corrente, in questo modo sei garantito (??) di avere il contesto giusto in seguito, indipendentemente dal thread in cui ti trovi.

 _uiCtx = SynchronizationContext.Current; 

E più tardi nel tuo compito usalo per interagire con il thread principale dell’interfaccia utente

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

Ho creato una class per questo. Sembra questo:

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

All’avvio della mia app chiamo UIContext.Initialize ()

E quando ne ho bisogno in un’attività, metto solo UIContext.Current come TaskScheduler.

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