从给定的线程获取SynchronizationContext

我似乎没有找到如何获取给定ThreadSynchronizationContext

 Thread uiThread = UIConfiguration.UIThread; SynchronizationContext context = uiThread.Huh?; 

我为什么需要那个?

因为我需要从前端应用程序的不同位置发布到UIThread。 所以我在名为UIConfiguration的类中定义了一个静态属性。 我在Program.Main方法中设置了这个属性:

 UIConfiguration.UIThread = Thread.CurrentThread; 

在那一刻我可以肯定我有正确的线程,但是我不能设置像这样的静态属性

 UIConfiguration.SynchronizationContext = SynchronizationContext.Current 

因为尚未安装该类的WinForms实现。 由于每个线程都有自己的SynchronizationContext,因此必须可以从给定的Thread对象中检索它,否则我完全错了?

这是不可能的。 问题是SynchronizationContextThread实际上是两个完全独立的概念。

虽然Windows Forms和WPF都为主线程设置了SynchronizationContext ,但大多数其他线程都没有。 例如,ThreadPool中没有任何线程包含自己的SynchronizationContext(当然,除非您自己安装)。

SynchronizationContext也可能与线程和线程完全无关 。 可以轻松设置同步上下文,以同步到外部服务或整个线程池等。

在您的情况下,我建议您在初始的主窗体的Loaded事件中设置UIConfiguration.SynchronizationContext 。 保证在该点开始上下文,并且在任何情况下消息泵启动之前将不可用。

我知道这是一个老问题,并为死灵道歉,但我刚刚找到了这个问题的解决方案,我认为这对我们这些一直在谷歌搜索的人来说可能是有用的(并且它不需要一个Control实例)。

基本上,您可以创建WindowsFormsSynchronizationContext的实例并在Main函数中手动设置上下文,如下所示:

  _UISyncContext = new WindowsFormsSynchronizationContext(); SynchronizationContext.SetSynchronizationContext(_UISyncContext); 

我在我的应用程序中完成了这项工作,它完美无缺。 但是,我应该指出我的Main标有STAThread,所以如果您的Main标记为MTAThread,我不确定它是否仍然有用(或者如果它甚至是必要的话)。

编辑:我忘了提及它,但_UISyncContext已经在我的应用程序的Program类的模块级别定义。

我发现最简洁,最有帮助的是Alex Davies的书中的以下段落“Async in C#5.0.O’Reilly Publ。,2012”,p.48-49:

  • “ SynchronizationContext是.NET Framework提供的类 ,它具有在特定类型的线程中运行代码的能力。
    .NET使用各种同步上下文,其中最重要的是WinForms和WPF使用的UI线程上下文。“

  • SynchronizationContext本身的实例本身没有做任何非常有用的事情,因此它的所有实际实例都倾向于是子类。

    它还具有静态成员,可以让您读取和控制当前的SynchronizationContext

    当前的SynchronizationContext是当前线程的属性。

    这个想法是,在任何一个你在特殊线程中运行的时候,你应该能够获得当前的SynchronizationContext并存储它。 稍后,您可以使用它在您启动的特殊线程上运行代码。 所有这一切都应该是可能的, 而不需要确切地知道你开始使用哪个线程,只要你可以使用SynchronizationContext,你就可以回到它

    SynchronizationContext的重要方法是Post ,它可以使委托在正确的上下文中运行“

  • “ 有些SynchronizationContexts封装了一个线程,就像UI线程一样 。
    有些封装了特定类型的线程 – 例如,线程池 – 但可以选择任何线程来发布委托 。 有些实际上并没有更改代码运行的线程,但仅用于监视,如ASP.NET同步上下文“

我不相信每个线程都有自己的SynchronizationContext – 它只有一个线程本地的SynchronizationContext

为什么不在UIConfiguration.UIThreadLoaded事件中设置UIConfiguration.UIThread ,或类似的东西?

用于从ThreadExecutionContext获取SynchronizationContext完整且有效的扩展方法(如果不存在则为null ),或者来自Dispatcher 。 在.NET 4.6.2上测试。

 using Ectx = ExecutionContext; using Sctx = SynchronizationContext; using Dctx = DispatcherSynchronizationContext; public static class _ext { // DispatcherSynchronizationContext from Dispatcher public static Dctx GetSyncCtx(this Dispatcher d) => d?.Thread.GetSyncCtx() as Dctx; // SynchronizationContext from Thread public static Sctx GetSyncCtx(this Thread th) => th?.ExecutionContext?.GetSyncCtx(); // SynchronizationContext from ExecutionContext public static Sctx GetSyncCtx(this Ectx x) => __get(x); /* ... continued below ... */ } 

所有上述函数最终调用下面显示的__get代码,这需要一些解释。

请注意, __get是一个静态字段,使用可丢弃的lambda块进行预初始化。 这允许我们整齐地拦截第一个调用者,以便运行一次性初始化,这将准备一个更快且无reflection的微小且永久的替换委托。

无畏初始化工作的最后一步是将替换替换为’__get’,这同时和悲惨地意味着代码丢弃自身,不留痕迹,所有后续调用者直接进入DynamicMethod ,甚至没有一丝旁路逻辑。

 static Func __get = arg => { // Hijack the first caller to do initialization... var fi = typeof(Ectx).GetField( "_syncContext", // private field in 'ExecutionContext' BindingFlags.NonPublic|BindingFlags.Instance); var dm = new DynamicMethod( "foo", // (any name) typeof(Sctx), // getter return type new[] { typeof(Ectx) }, // type of getter's single arg typeof(Ectx), // "owner" type true); // allow private field access var il = dm.GetILGenerator(); il.Emit(OpCodes.Ldarg_0); il.Emit(OpCodes.Ldfld, fi); il.Emit(OpCodes.Ret); // ...now replace ourself... __get = (Func)dm.CreateDelegate(typeof(Func)); // oh yeah, don't forget to handle the first caller's request return __get(arg); // ...never to come back here again. SAD! }; 

可爱的部分是最终的位置 – 为了实际获取抢先的第一个调用者的值 – 该函数表面上用自己的参数调用自身,但避免通过在之前立即替换自身来进行递归。

没有特别的理由在本页讨论的SynchronizationContext的特定问题上演示这种不寻常的技术。 从ExecutionContext _syncContext字段可以通过传统的reflection(加上一些扩展方法结霜)轻松而简单地解决。 但我想我会分享这种方法,我个人已经使用了很长一段时间,因为它也很容易适应,同样广泛适用于这种情况。

当访问非公共领域需要极端性能时,这是特别合适的。 我想我最初在一个基于QPC的频率计数器中使用它,在一个紧密的循环中读取磁场,每20或25纳秒迭代一次,这对传统的reflection来说是不可能的。

主要答案总结如下,但下面我提到了一些有趣的观点,与提问者的调查不太相关,更多的是刚刚展示的技术。


运行时呼叫者

为清楚起见,我将“安装交换”和“第一次使用”步骤分成上面代码中的两个单独的行,而不是我自己的代码中的内容(以下版本也避免了一次主内存提取与前一次相比) ,可能涉及线程安全,请参阅下面的详细讨论):

 return (__get = (Func)dm.CreateDel...(...))(arg); 

换句话说,所有调用者( 包括第一个调用者)都以完全相同的方式获取值,并且没有使用reflection代码来执行此操作。 它只写替换的getter 。 感谢il-visualizer ,我们可以在运行时在调试器中看到DynamicMethod的主体:

ldarg.0 <br /> ldfld SynchronizationContext _syncContext / ExecutionContext <br /> ret”> </p>
<p>  <strong>无锁螺纹安全</strong> </p>
<p> 我应该注意,在.NET 内存模型和无锁理念下,交换函数体是一个完全线程安全的操作。 后者有利于进行前瞻性保证,可能会造成重复或冗余的工作。 在完全合理的理论基础上正确允许多向赛车初始化: </p>
<ul>
<li> 竞争入口点(初始化代码)是全局预配置和保护(由.NET加载器),以便(多个)竞争者(如果有的话)进入相同的初始化程序,永远不会被视为<code>null</code> 。 </li>
<li> 多种族产品(吸气剂)总是在逻辑上相同,所以任何特定赛车手(或后来的非赛车来电者)碰巧接听哪一个都无关紧要,甚至是否有任何赛车最终使用他们自己生产的赛车手; </li>
<li> 每个安装交换都是一个大小为<code>IntPtr</code>的单个存储,对于任何相应的平台位数,它都保证是primefaces的; </li>
<li> 最后,技术上<em>对于完善forms正确性至关重要</em> ,“失败者”的工作产品由<code>GC</code>回收,因此不会泄漏。 在这种类型的比赛中 ,失败者是除了<strong><em>最后一名</em></strong>选手之外的每一个赛车手(因为其他人的努力都是轻率的,并且总是用相同的结果覆盖)。 </li>
</ul>
<p> 虽然我相信这些要点可以完全保护在各种可能情况下编写的代码,如果您仍然怀疑或担心总体结论,您可以随时添加额外的防弹层: </p>
<pre> <code>var tmp = (Func<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>)); Thread.MemoryBarrier(); __get = tmp; return tmp(arg);</code> </pre>
<p> 这只是一个paraniod版本。 与早期的浓缩单行内容一样,.NET内存模型保证只有一个<strong>存储</strong> – 零<strong>读取</strong> – 到’__get’的位置。  (顶部的完整扩展示例确实进行了额外的主内存提取,但由于第二个要点仍然是正确的)正如我所提到的,这对于正确性来说都不是必需的,但理论上它可能会给出一个微不足道的信息。性能奖励:通过最终结束比赛,在极少数情况下,激进的同花顺可以防止后续的呼叫者在脏的高速缓存线上进行不必要的(但同样无害的)比赛。 </p>
<p>  <strong>双置信转换</strong> </p>
<p> 调用最终的超快速方法仍然是通过前面显示的静态扩展方法进行的。 这是因为我们还需要以某种方式表示在编译时实际存在的入口点,以便编译器绑定和传播元数据。 对于自定义代码的强类型元数据和智能感知的压倒性便利,双重thunk是一个很小的代价,直到运行时才能实际解析。 然而它的运行速度至少和静态编译的代码一样快,在每个调用中进行一堆reflection的速度要快得多,所以我们可以充分利用这两个世界! </p>

</div><!-- #comment-## -->

	<div class=