从给定的线程获取SynchronizationContext
我似乎没有找到如何获取给定Thread
的SynchronizationContext
:
Thread uiThread = UIConfiguration.UIThread; SynchronizationContext context = uiThread.Huh?;
我为什么需要那个?
因为我需要从前端应用程序的不同位置发布到UIThread。 所以我在名为UIConfiguration
的类中定义了一个静态属性。 我在Program.Main
方法中设置了这个属性:
UIConfiguration.UIThread = Thread.CurrentThread;
在那一刻我可以肯定我有正确的线程,但是我不能设置像这样的静态属性
UIConfiguration.SynchronizationContext = SynchronizationContext.Current
因为尚未安装该类的WinForms实现。 由于每个线程都有自己的SynchronizationContext,因此必须可以从给定的Thread
对象中检索它,否则我完全错了?
这是不可能的。 问题是SynchronizationContext
和Thread
实际上是两个完全独立的概念。
虽然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.UIThread
的Loaded
事件中设置UIConfiguration.UIThread
,或类似的东西?
用于从Thread
或ExecutionContext
获取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
的主体: