主线程的 SynchronizationContext.Current 如何在 Windows 窗体应用程序中变为空?

Posted

技术标签:

【中文标题】主线程的 SynchronizationContext.Current 如何在 Windows 窗体应用程序中变为空?【英文标题】:How can SynchronizationContext.Current of the main thread become null in a Windows Forms application? 【发布时间】:2011-06-07 06:03:39 【问题描述】:

我的应用程序有一个问题:在某些时候,主线程的 SynchronizationContext.Current 变为 null。我无法在一个孤立的项目中重现同样的问题。我的实际项目很复杂;它混合了 Windows 窗体和 WPF 并调用 WCF Web 服务。据我所知,这些都是可能与 SynchronizationContext 交互的系统。

这是我独立项目的代码。我真正的应用程序做了类似的事情。但是,在我的真实应用程序中,当执行延续任务时,主线程上的 SynchronizationContext.Current 为 null。

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

什么可能导致主线程的 SynchronizationContext.Current 变为 null?

编辑:

@Hans 请求堆栈跟踪。这里是:

在 d:\sources\s2\Framework\Sources\UI\Commands\AsyncCommand.cs:line 157 中的 MyApp.Framework.UI.Commands.AsyncCommand.HandleTaskError(任务任务) 在 System.Threading.Tasks.Task.c__DisplayClassb.b__a(对象 obj) 在 System.Threading.Tasks.Task.InnerInvoke() 在 System.Threading.Tasks.Task.Execute() 在 System.Threading.Tasks.Task.ExecutionContextCallback(对象 obj) 在 System.Threading.ExecutionContext.runTryCode(对象 userData) 在 System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode 代码,CleanupCode backoutCode,对象 userData) 在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext,ContextCallback 回调,对象状态) 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback 回调,对象状态,布尔 ignoreSyncCtx) 在 System.Threading.Tasks.Task.ExecuteWithThreadLocal(任务和 currentTaskSlot) 在 System.Threading.Tasks.Task.ExecuteEntry(布尔 bPreventDoubleExecution) 在 System.Threading.Tasks.SynchronizationContextTaskScheduler.PostCallback(对象 obj) 在 System.RuntimeMethodHandle._InvokeMethodFast(IRuntimeMethodInfo 方法,对象目标,Object[] 参数,SignatureStruct& sig,MethodAttributes methodAttributes,RuntimeType typeOwner) 在 System.RuntimeMethodHandle.InvokeMethodFast(IRuntimeMethodInfo 方法,对象目标,对象 [] 参数,签名 sig,MethodAttributes 方法属性,RuntimeType typeOwner) 在 System.Reflection.RuntimeMethodInfo.Invoke(Object obj,BindingFlags invokeAttr,Binder binder,Object[] 参数,CultureInfo 文化,布尔型 skipVisibilityChecks) 在 System.Delegate.DynamicInvokeImpl(对象 [] 参数) 在 System.Windows.Forms.Control.InvokeMarshaledCallbackDo(ThreadMethodEntry 时间) 在 System.Windows.Forms.Control.InvokeMarshaledCallbackHelper(对象 obj) 在 System.Threading.ExecutionContext.runTryCode(对象 userData) 在 System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode 代码,CleanupCode backoutCode,对象 userData) 在 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext,ContextCallback 回调,对象状态) 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback 回调,对象状态,布尔 ignoreSyncCtx) 在 System.Threading.ExecutionContext.Run(ExecutionContext executionContext,ContextCallback 回调,对象状态) 在 System.Windows.Forms.Control.InvokeMarshaledCallback(ThreadMethodEntry 时间) 在 System.Windows.Forms.Control.InvokeMarshaledCallbacks() 在 System.Windows.Forms.Control.WndProc(消息和 m) 在 System.Windows.Forms.Control.ControlNativeWindow.OnMessage(消息& m) 在 System.Windows.Forms.Control.ControlNativeWindow.WndProc(消息和 m) 在 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd,Int32 msg,IntPtr wparam,IntPtr lparam) 在 System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(味精和味精) 在 System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(IntPtr dwComponentID,Int32 原因,Int32 pvLoopData) 在 System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 原因,ApplicationContext 上下文) 在 System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 原因,ApplicationContext 上下文) 在 System.Windows.Forms.Application.Run(窗体 mainForm) 在 d:\sources\s2\Framework\Sources\UI\SharedUI\ApplicationBase.cs:line 190 中的 MyApp.Framework.SharedUI.ApplicationBase.InternalStart() 在 d:\sources\s2\Framework\Sources\UI\SharedUI\ApplicationBase.cs:line 118 中的 MyApp.Framework.SharedUI.ApplicationBase.Start() 在 d:\sources\s2\App1\Sources\WinUI\HDA.cs:line 63 中的 MyApp.App1.WinUI.HDA.Main()

【问题讨论】:

在 UpdateGUI 上设置断点并发布堆栈跟踪。 看起来完全正常。我无法解释为什么 TaskScheduler.FromCurrentSynchronizationContext() 不起作用。这是为 Control.InvokeMarshaledCallback() 调用提供 SC 的那个。它已经检查了空值。假设您使用的是 .NET 4.0 “Dispatcher”,你是在混合 Winforms 和 WPF 代码吗? @Sly 你得到这个问题的答案了吗?我在这里遇到同样的问题>. @MichelAyres:我们从未找到根本原因,因此决定通过将SynchronizationContext.Current 复制到_syncContext 变量来解决此问题。然后,在任务回调中我们执行if (SynchronizationContext.Current == null) SynchronizationContext.SetSynchronizationContext(_syncContext); 。你看过@Dan的回答吗?我从来没有尝试过,这就是为什么我还没有接受他的回答。如果您使用 .Net 4.5 对其进行测试并且如果它有效,请告诉我,我会接受 Dan 的回答。 【参考方案1】:

Sly,当混合使用 WPF、WCF 和 TPL 时,我遇到了完全相同的行为。 Main 线程当前的 SynchronizationContext 在少数情况下会变为 null。

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

根据 msdn 论坛上的this post,这是 4.0 中 TPL 中已确认的错误。一位同事在 4.5 上运行,但没有看到此行为。

我们通过使用 FromCurrentSynchronizationContext 在主线程中创建一个静态单例中的 TaskScheduler 来解决这个问题,然后在创建延续时始终引用该任务调度程序。例如

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

这避免了 .net 4.0 上的 TPL 中的问题。

更新 如果您在开发机器上安装了 .net 4.5,即使您的目标是 4.0 框架,您也不会看到此问题。仅安装 4.0 的用户仍会受到影响。

【讨论】:

我遇到了这个错误——我发布了一个简短的 Winforms 程序,演示了重现该问题的简单方法。 ***.com/questions/11621372/… 这里针对 4.5.2,还是会弹出这个错误信息。但并非所有任务...【参考方案2】:

不确定这是否是首选方法,但这是我使用 SynchronizationContext 的方式:

在您的构造函数(主线程)中保存当前上下文的副本,这样可以保证(??)以后无论您在哪个线程上都有正确的上下文。

_uiCtx = SynchronizationContext.Current;

稍后在您的任务中使用它与主 UI 线程进行交互

_uiCtx.Post( ( o ) =>

 //UI Stuff goes here
, null );

【讨论】:

这肯定会起作用,但这是一种解决方法。我想弄清楚主线程是如何丢失 SynchronizationContext 的。【参考方案3】:

我为此创建了一个类。它看起来像这样:

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

在我的应用程序启动时,我调用 UIContext.Initialize()

当我在任务中需要它时,我只需将 UIContext.Current 作为 TaskScheduler。

Task.Factory.StartNew(() =>

    //Your code here
, CancellationToken.None, TaskCreationOptions.None, UIContext.Current);

【讨论】:

在遇到此问题的 Unity (5.5) 项目中表现出色。谢谢!

以上是关于主线程的 SynchronizationContext.Current 如何在 Windows 窗体应用程序中变为空?的主要内容,如果未能解决你的问题,请参考以下文章

主线程啥都没做,就会等待子线程结束。这是为啥?

Android 异步操作Android 线程切换 ( 判定当前线程是否是主线程 | 子线程中执行主线程方法 | 主线程中执行子线程方法 )

java 子线程 回调 主线程

子线程怎么不阻塞主线程

QT中UI主窗口如何与子线程相互传递参数

C++怎么在主线程中使用子线程的数据? 比如说主线程中有一个数组,如何在子线程中调用这个数组