在主UI线程的Continuation中,SynchronizationContext.Current为null

我一直在尝试在Winforms应用程序中追踪以下问题:
SynchronizationContext.Current在任务的继续(即.ContinueWith )中为空,它在主线程上运行(我希望当前的同步上下文是System.Windows.Forms.WindowsFormsSynchronizationContext )。

以下是演示此问题的Winforms代码:

 using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace WindowsFormsApplication1 { public partial class Form1 : Form { public Form1() { InitializeComponent(); TaskScheduler ts = TaskScheduler.FromCurrentSynchronizationContext(); // Get the UI task scheduler // This line is required to see the issue (Removing this causes the problem to go away), since it changes the codeflow in // \SymbolCache\src\source\.NET\4\DEVDIV_TFS\Dev10\Releases\RTMRel\ndp\clr\src\BCL\System\Threading\ExecutionContext.cs\1305376\ExecutionContext.cs // at line 435 System.Diagnostics.Trace.CorrelationManager.StartLogicalOperation("LogicalOperation"); var task = Task.Factory.StartNew(() => { }); var cont = task.ContinueWith(MyContinueWith, CancellationToken.None, TaskContinuationOptions.None, ts); System.Diagnostics.Trace.CorrelationManager.StopLogicalOperation(); } void MyContinueWith(Task t) { if (SynchronizationContext.Current == null) // The current SynchronizationContext shouldn't be null here, but it is. MessageBox.Show("SynchronizationContext.Current is null"); } } } 

这是一个问题,因为我尝试从延续中使用BackgroundWorker ,而BackgroundWorker将使用当前的SynchronizationContext作为其事件RunWorkerCompletedProgressChanged 。 由于当我启动BackgroundWorker时当前的SynchronizationContext为null,因此事件不会像我想要的那样在主ui线程上运行。

我的问题:
这是微软代码中的错误,还是我在某处犯了错误?

附加信息:

  • 我正在使用.Net 4.0(我还没有尝试过.NET 4.5 RC)
  • 我可以在任何x86 / x64 /任何CPU(在x64机器上)上的Debug / Release上重现这一点。
  • 它一致地再现(如果有人不能重现它,我会感兴趣)。
  • 我有使用BackgroundWorker的遗留代码 – 所以我不能轻易转换为不使用BackgroundWorker
  • 我已经确认MyContinueWith中的代码正在主ui线程上运行。
  • StartLogicalOperation地知道为什么StartLogicalOperation调用有助于引起问题,这正是我在应用程序中缩小它的范围。

这个问题在.NET 4.5 RC中得到修复(刚刚测试过)。 所以我认为这是.NET 4.0中的一个错误。 另外,我猜这些post引用了同样的问题:

那真不幸。 现在我必须考虑解决方法。

编辑:
从调试到.Net源代码,我对问题何时重现有了更好的理解。 以下是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); } } 

当我们进入调用RunInternal的“else”子句时,该问题才会重现。 这是因为RunInternal最终取代了ExecutionContext,它具有改变当前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; } } 

因此,对于我的具体情况,这是因为`executionContext.IsDefaultFTContext(ignoreSyncCtx)行返回false。 这是代码:

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

对我来说,由于_logicalCallContext.HasInfo为真,因此返回false。 这是代码:

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

对我来说,这是因为HasUserData是真的而返回true。 这是代码:

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

对我来说,由于我调用了Diagnostics.Trace.CorrelationManager.StartLogicalOperation("LogicalOperation"); ,m_DataStore中会有项目Diagnostics.Trace.CorrelationManager.StartLogicalOperation("LogicalOperation");

总之,看起来有几种不同的方法可以让bug重现。 希望这个例子可以帮助其他人确定他们是否遇到了同样的错误。