How can SynchronizationContext.Current of the main thread become null in a Windows Forms application?
Asked Answered
C

3

40

I have a problem in my application: At some point, the SynchronizationContext.Current becomes null for the main thread. I'm unable to reproduce the same problem in an isolated project. My real project is complex; it mixes Windows Forms and WPF and calls WCF Web Services. As far as I know, those are all systems that may interact with the SynchronizationContext.

This is the code from my isolated project. My real app does something that resembles that. However, in my real app the SynchronizationContext.Current is null on the main thread when the continuation task is executed.

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

What could cause the SynchronizationContext.Current of the main thread to become null?

Edit:

@Hans asked for the stack trace. Here it is:


   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

Cyrano answered 11/1, 2011 at 15:27 Comment(15)
Set a breakpoint on UpdateGUI and post the stack trace.Checkerboard
@Hans: UpdateGUI() is in my sample project. In my real project, the method is called HandleTaskError(). I have posted the stack trace in my question.Cyrano
Looks entirely normal. I have no explanation why TaskScheduler.FromCurrentSynchronizationContext() doesn't work. That's the one that provides the SC for the Control.InvokeMarshaledCallback() call. It already checks for a null. Assuming you are using .NET 4.0Checkerboard
@Hans: In fact TaskScheduler.FromCurrentSynchronizationContext() did return the right scheduler at the moment of chaining the tasks together. And at runtime, the continuation task was executed on the main thread. But in HandleTaskError(), SynchronizationContext.Current is null... And yes, I'm using .Net 4.0.Cyrano
"Dispatcher", are you mixing Winforms and WPF code?Checkerboard
"Hans: Sorry I meant "scheduler". (But yes I'm mixing Windows Form and WPF)Cyrano
Hmm, wrong synchronization provider, you got WPF's not Winforms'. Sounds to me you broke the warranty.Checkerboard
@Hans: Can you expand on that? Do you think it's a bug in my code or in the framework? If I set a breakpoint where the task chaining is done, SynchronizationContext.Current returns a WindowsFormsSynchronizationContext and TaskScheduler.FromCurrentSynchronizationContext() returns a SynchronizationContextTaskScheduler. Why do you say I get WPF's synchronizer?Cyrano
Because you said "Dispatcher". Look at the private m_synchronizationContext field of the SynchrizationContextTaskScheduler object to see what provider is being used.Checkerboard
@Hans: First, thank you for helping me. I checked and m_synchronizationContext is a WindowsFormsSynchronizationContext. So that seems all right.Cyrano
Well, that sounded good for a while. I'm out of theories without a repro that I can debug.Checkerboard
@Hans: I'm affraid I'll have to open a case with Microsoft Support if I want to find the root cause. For now I changed my code and did what @kbeal2k did in his answer.Cyrano
@Sly did you got an answer for this problem? I'm having the same issue here >.<Antibiosis
@MichelAyres: We never found the root cause and decided to work around the problem by copying the SynchronizationContext.Current to a _syncContext variable. Then, in the task callback we do if (SynchronizationContext.Current == null) { SynchronizationContext.SetSynchronizationContext(_syncContext); }. Did you look at @Dan's answer? I never got to trying it, which is why I still have not accepted his answer yet. If you tests it with .Net 4.5 and if it works, let me know and I'll accept Dan's answer.Cyrano
Doing the work with 4.0 here >.< I didn't try (@Dan's answer)[https://mcmap.net/q/397397/-how-can-synchronizationcontext-current-of-the-main-thread-become-null-in-a-windows-forms-application] yet. Was trying to find a "right" way, not a workaround =X Thanks for the explanation, going to try Dan's answer and your suggestion as well.Antibiosis
D
44

Sly, I have run into the exact same behavior when a mixture of WPF, WCF, and TPL is used. The Main thread's current SynchronizationContext will become null in a few 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();

According to this post on the msdn forums, this is a confirmed bug in the TPL in 4.0. A coworker is running on 4.5 and does not see this behavior.

We solved this by creating a TaskScheduler in a static singleton with the main thread using FromCurrentSynchronizationContext and then always reference that task scheduler when creating continuations. For example

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

This avoids the issue in the TPL on .net 4.0.

Update If you have .net 4.5 installed on your development machine, you will not see this issue even if you are targeting the 4.0 framework. Your users who only have 4.0 installed will still be affected.

Depressed answered 8/5, 2012 at 21:30 Comment(2)
I experienced this bug--I've posted a short Winforms program that demonstrates an easy way to reproduce the issue. #11621872Horehound
targeting 4.5.2 here, still have this error message pop up. Not on all tasks though...Termination
H
12

Not sure if this is the preferred method but here is how I use the SynchronizationContext:

In you constructor (main thread) save a copy of the current context, that way you are guaranteed(??) to have the right context later no matter what thread you are on.

_uiCtx = SynchronizationContext.Current;

And later in your Task use it to do interact with the main UI thread

_uiCtx.Post( ( o ) =>
{
 //UI Stuff goes here
}, null );
Halflight answered 11/1, 2011 at 17:48 Comment(1)
This would work for sure but that would be a workaround. I'd like to figure out how the main thread lost its SynchronizationContext.Cyrano
P
7

I've created a class for this. It looks like this:

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

On startup of my App I call UIContext.Initialize()

And when I need it in a task I just put UIContext.Current as TaskScheduler.

Task.Factory.StartNew(() =>
{
    //Your code here
}, CancellationToken.None, TaskCreationOptions.None, UIContext.Current);
Piscatorial answered 13/9, 2013 at 9:25 Comment(1)
Worked great on a Unity (5.5) project that was having this exact problem. Thanks!Naoise

© 2022 - 2024 — McMap. All rights reserved.