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

21
我一直在尝试追踪Winforms应用程序中的以下问题: 在主线程上运行的任务的继续操作(即.ContinueWith)中,SynchronizationContext.Current为null(我期望当前同步上下文为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");
        }
    }
}

这对我来说是个问题,因为我尝试从 continuation 使用 BackgroundWorker,而 BackgroundWorker 将使用当前 SynchronizationContext 作为其事件 RunWorkerCompleted 和 ProgressChanged。由于在启动 BackgroundWorker 时当前的 SynchronizationContext 为空,所以事件不会像我打算的那样在主 UI 线程上运行。
我的问题是:这是 Microsoft 代码中的 bug 还是我犯了什么错误?
其他信息: - 我正在使用 .Net 4.0(我还没有在 .NET 4.5 RC 上尝试过)。 - 我可以在 x86/x64/Any CPU(在 x64 机器上)的 Debug/Release 中重现此问题。 - 它总是能够被重现(如果有人不能重现它,我会很感兴趣)。 - 我有使用 BackgroundWorker 的旧代码,所以我不能轻易地改用其他方法。 - 我已确认 MyContinueWith 中的代码正在主 UI 线程上运行。 - 我不知道为什么 StartLogicalOperation 调用有助于引起此问题,这只是我在应用程序中缩小到的问题。

你从那个链接得到了诊断结果。你是否也将WPF或WCF代码混合到这个Winforms应用程序中? - Hans Passant
1个回答

21
问题已在.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);
            } 
        } 

问题只在进入“else”子句时复现,该子句调用RunInternal。这是因为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; 
    }
}

对我来说,这个代码返回true是因为HasUserData为true。以下是代码:

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

对我而言,m_DataStore中会有项目,因为我调用了Diagnostics.Trace.CorrelationManager.StartLogicalOperation("LogicalOperation");

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


1
你可以联系微软 PSS,看看是否能够获取热补丁。 - Peter Ritchie
3
我在这里提交了一个错误报告:https://connect.microsoft.com/VisualStudio/feedback/details/755320/synchronizationcontext-current-is-null-in-continuation-on-the-main-ui-thread。 - Matt Smith
Matt,你在4.0中找到解决方法了吗?我有一个必须使用4.0的可移植类(Profile 158),在这个类中我遇到了这个问题。啊,我是在NUnit测试中进行这个操作,可能会有诊断跟踪调用... - tofutim
@tofutim 我在这里发布了我正在使用的解决方法:https://dev59.com/S3rZa4cB1Zd3GeqP9fmw#20953283 - Matt Smith
谢谢Matt。我遇到的问题是,我正在使用一个不支持其中SetSynchronizationContext的可移植类(配置文件158)。最终,我在我的类第一次被调用时保存了TaskScheduler并使用它。 - tofutim
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接