How Can Synchronizationcontext.Current of the Main Thread Become Null in a Windows Forms Application

SynchronizationContext.Current is null on Main thread

Of course there is no SynchronizationContext available when the entry point (Main) of your application is hit. You need to wait for the framework to initialize it.

In a Windows Forms application this happens when the first form is created. So only after the Application.Run method has been called, the SynchronizationContext.Current property will actually return a synchronization context.

SynchronizationContext.Current is null in Continuation on the main UI thread

The issue is fixed in .NET 4.5 RC (just tested it). So I assume it is a bug in .NET 4.0.
Also, I'm guessing that these posts are referencing the same issue:

  • How can SynchronizationContext.Current of the main thread become null in a Windows Forms application?
  • http://social.msdn.microsoft.com/Forums/en-US/wcf/thread/629d5524-c8db-466f-bc27-0ced11b441ba

That's unfortunate. Now I have to consider workarounds.

Edit:
From debugging into the .Net source, I have a little better understanding of when the issue would reproduce. Here's some relevant code from ExecutionContext.cs:

        internal static void Run(ExecutionContext executionContext, ContextCallback callback,  Object state, bool ignoreSyncCtx) 
{
// ... Some code excluded here ...

ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate();
if ( (ec == null || ec.IsDefaultFTContext(ignoreSyncCtx)) &&
#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK
SecurityContext.CurrentlyInDefaultFTSecurityContext(ec) &&
#endif // #if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK
executionContext.IsDefaultFTContext(ignoreSyncCtx))
{
callback(state);
}
else
{
if (executionContext == s_dummyDefaultEC)
executionContext = s_dummyDefaultEC.CreateCopy();
RunInternal(executionContext, callback, state);
}
}

The issue only reproduces when we get into the "else" clause which calls RunInternal. This is because the RunInternal ends up replacing the the ExecutionContext which has the effect of changing what the current SynchronizationContext:

        // Get the current SynchronizationContext on the current thread 
public static SynchronizationContext Current
{
get
{
SynchronizationContext context = null;
ExecutionContext ec = Thread.CurrentThread.GetExecutionContextNoCreate();
if (ec != null)
{
context = ec.SynchronizationContext;
}

// ... Some code excluded ...
return context;
}
}

So, for my specific case, it was because the line `executionContext.IsDefaultFTContext(ignoreSyncCtx)) returned false. Here's that code:

        internal bool IsDefaultFTContext(bool ignoreSyncCtx)
{
#if FEATURE_CAS_POLICY
if (_hostExecutionContext != null)
return false;
#endif // FEATURE_CAS_POLICY
#if FEATURE_SYNCHRONIZATIONCONTEXT
if (!ignoreSyncCtx && _syncContext != null)
return false;
#endif // #if FEATURE_SYNCHRONIZATIONCONTEXT
#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK
if (_securityContext != null && !_securityContext.IsDefaultFTSecurityContext())
return false;
#endif //#if FEATURE_IMPERSONATION || FEATURE_COMPRESSEDSTACK
if (_logicalCallContext != null && _logicalCallContext.HasInfo)
return false;
if (_illogicalCallContext != null && _illogicalCallContext.HasUserData)
return false;
return true;
}

For me, that was returning false due to _logicalCallContext.HasInfo was true. Here's that code:

public bool HasInfo
{
[System.Security.SecurityCritical] // auto-generated
get
{
bool fInfo = false;

// Set the flag to true if there is either remoting data, or
// security data or user data
if(
(m_RemotingData != null && m_RemotingData.HasInfo) ||
(m_SecurityData != null && m_SecurityData.HasInfo) ||
(m_HostContext != null) ||
HasUserData
)
{
fInfo = true;
}

return fInfo;
}
}

For me, this was returning true because HasUserData was true. Here's that code:

    internal bool HasUserData
{
get { return ((m_Datastore != null) && (m_Datastore.Count > 0));}
}

For me, the m_DataStore would have items in it due to my call to Diagnostics.Trace.CorrelationManager.StartLogicalOperation("LogicalOperation");

In summary, it looks like there are several different ways you could get the bug to reproduce. Hopefully, this example will serve to help others in determining if they are running into this same bug or not.

Why is SynchronizationContext.Current null?

Your code critically depends on exactly when and where the constructor of your class runs. SynchronizationContext.Current will be null when:

  • your class object is created too soon, before your code creates an instance of the Form class or calls Application.Run() in Main(). That's when the Current member is set to an instance of WindowsFormsSynchronizationContext, the class that knows how to marshal calls with the message loop. Fix this by moving your object instancing code to the main form constructor.

  • your class object is created on any thread other than the main UI thread. Only the UI thread in a Winforms application can marshal calls. Diagnose this by adding a constructor to your class with this statement:

      Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);

Also add this line to the Main() method in Program.cs. It won't work if the displayed value in the Output window is different. Fix this by moving your object instancing code to, again, the main form constructor so you can be sure it runs on the UI thread.

Why is SynchronizationContext.Current null in my Winforms application?

See this explanation.

SynchronizationContext.Current is only set in the main thread (which is the only thread where you don't actually need it)

The blog post proposes a workaround.

Workaround for issue in .NET 4.0 where SynchronizationContext.Current is null

I created several extension methods that matched ContinueWith and StartNew except that they also take an additional SyncronizationContext. I then use this argument to restore the expected SynchronizationContext before executing the action:

Below, I've given examples:

public static class TaskExtensionMethods
{
public static Task ContinueWith_UsingSyncContextWorkaround(this Task task, Action<Task> continuationAction, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler, SynchronizationContext sc)
{
Action<Task> actionWithWorkaround = t =>
{
SynchronizationContext.SetSynchronizationContext(sc);
continuationAction(t);
};

return task.ContinueWith(actionWithWorkaround, cancellationToken, continuationOptions, scheduler);
}

public static Task StartNew_UsingSyncContextWorkaround(this TaskFactory taskFactory, Action action, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler, SynchronizationContext sc)
{
Action actionWithWorkaround = () =>
{
SynchronizationContext.SetSynchronizationContext(sc);
action();
};

return taskFactory.StartNew(actionWithWorkaround, cancellationToken, creationOptions, scheduler);
}
}

I then use these extension methods in place of .ContinueWith or .StartNew

Related Question:

  • How to create a generic Task.ContinueWith extension method

thread does not work, it's freezing my application

You don't need to use Thread.Abort(), SynchronizationContext or even use "asynchronous" code (I assume you're referring to await/async which you cannot call unless your target API actually provides true async functionality, note that using Task.Run is not the same thing): WinForms has built-in functionality for running code in the UI thread in the Invoke/BeginInvoke methods.

For progress reporting, I don't recommend passing-around a ProgressBar as that's a brittle design and means your inner business logic has a dependency on WinForms which prevents you from using it in a WPF, ASP.NET or headless process; instead you could have a private method that updates the UI via a callback, like so:

private ProgressBar progressBar;

public Home()
{
this.InitializeComponent();
}

private void btnSend_Click( Object sender, EventArgs e )
{
Task.Run( (Action)this.LoadData )
}

private void UpdateProgress( Float progress )
{
if( this.InvokeRequired )
{
this.BeginInvoke( (Action<Float>)this.UpdateProgress, progress );
return;
}

this.progressBar.Value = progress * this.progressBar.Maximum;
}

private void LoadData()
{
ETLBusiness etlBusiness = new ETLBusiness(filePath);
etlBusiness.LoadData( this.UpdateProgress ); // You'll need to replace its progressBar parameter with a callback to `this.UpdateProgress`.
}

Where your ETLBusiness.LoadData method should be changed to this:

void LoadData( Action<Float> progressCallback );

Get SynchronizationContext from a given Thread

This is not possible. The problem is that a SynchronizationContext and a Thread are really two completely separate concepts.

While it's true that Windows Forms and WPF both setup a SynchronizationContext for the main thread, most other threads do not. For example, none of the threads in the ThreadPool contain their own SynchronizationContext (unless, of course, you install your own).

It's also possible for a SynchronizationContext to be completely unrelated to threads and threading. A synchronization context can easily be setup that synchronizes to an external service, or to an entire thread pool, etc.

In your case, I'd recommend setting your UIConfiguration.SynchronizationContext within the initial, main form's Loaded event. The context is guaranteed to be started at that point, and will be unusable until the message pump has been started in any case.



Related Topics



Leave a reply



Submit