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

时间:2012-07-23 22:17:23

标签: c# winforms task-parallel-library synchronizationcontext

我一直在尝试在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调用会导致问题,这正是我在应用程序中将其缩小的原因。

1 个答案:

答案 0 :(得分:20)

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

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

修改
从调试到.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会包含项目

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