从给定线程获取 SynchronizationContext

发布于 2024-10-01 00:18:10 字数 635 浏览 2 评论 0原文

我不知道如何获取给定 ThreadSynchronizationContext

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

为什么我需要它?

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

UIConfiguration.UIThread = Thread.CurrentThread;

在那一刻我可以确定我拥有正确的线程,但是我无法设置静态属性,

UIConfiguration.SynchronizationContext = SynchronizationContext.Current

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

I can't work out how to get the SynchronizationContext of a given Thread:

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

Why would I need that?

Because I need to post to the UIThread from different spots all across the front end application. So I defined a static property in a class called UIConfiguration. I set this property in the Program.Main method:

UIConfiguration.UIThread = Thread.CurrentThread;

In that very moment I can be sure I have the right thread, however I cannot set a static property like

UIConfiguration.SynchronizationContext = SynchronizationContext.Current

because the WinForms implementation of that class has not yet been installed. Since each thread has it's own SynchronizationContext, it must be possible to retrieve it from a given Thread object, or am I completely wrong?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

来世叙缘 2024-10-08 00:18:10

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

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

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

在您的情况下,我建议在初始主表单的 Loaded 中设置 UIConfiguration.SynchronizationContext事件。上下文保证在此时启动,并且在任何情况下直到消息泵启动之前都无法使用。

This is not possible. The problem is that a SynchronizationContext and a Thread are really two completely separate concepts.

While it's true that Windows Forms and WPF both setup a SynchronizationContext for the main thread, most other threads do not. For example, none of the threads in the ThreadPool contain their own SynchronizationContext (unless, of course, you install your own).

It's also possible for a SynchronizationContext to be completely unrelated to threads and threading. A synchronization context can easily be setup that synchronizes to an external service, or to an entire thread pool, etc.

In your case, I'd recommend setting your UIConfiguration.SynchronizationContext within the initial, main form's Loaded event. The context is guaranteed to be started at that point, and will be unusable until the message pump has been started in any case.

画中仙 2024-10-08 00:18:10

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

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

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

我已经在我的应用程序中完成了此操作,并且它运行完美,没有任何问题。但是,我应该指出,我的 Main 被标记为 STAThread,所以我不确定如果您的 Main 被标记,这是否仍然有效(或者是否有必要)用 MTAThread 代替。

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

I know this is an old question, and apologize for the necro, but I just found a solution to this problem that I figured might be useful for those of us who have been googling this (and it doesn't require a Control instance).

Basically, you can create an instance of a WindowsFormsSynchronizationContext and set the context manually in your Main function, like so:

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

I have done this in my application, and it works perfectly without issues. However, I should point out that my Main is marked with STAThread, so I am not sure if this will still work (or if it's even necessary) if your Main is marked with MTAThread instead.

EDIT: I forgot to mention it, but _UISyncContext is already defined at the module level in the Program class in my application.

羁〃客ぐ 2024-10-08 00:18:10

我发现 Alex Davies 所著的《Async in C# 5.0. O'Reilly Publ., 2012》,第 48-49 页中的以下段落是最简洁、最有帮助的:

  • "SynchronizationContext 是 .NET Framework 提供的类,它能够在特定类型的线程中运行代码< /strong>.
    .NET 使用多种同步上下文,其中最重要的是 WinForms 和 WPF 使用的 UI 线程上下文。"

  • "SynchronizationContext 本身的实例不执行任何非常有用的操作,所以它的所有实际实例往往都是子类。

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

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

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

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

  • 某些 SynchronizationContext 封装单个线程,如 UI 线程
    有些封装特定类型的线程 - 例如线程池 - 但可以选择其中任何线程将委托发布到。有些实际上并不会更改代码运行的线程,而仅用于监视,例如 ASP.NET 同步上下文"

I found as the most concise and helpful to me the following passages from book by Alex Davies "Async in C# 5.0. O'Reilly Publ., 2012", p.48-49:

  • "SynchronizationContext is a class provided by the .NET Framework, which has the ability to run code in a particular type of thread.
    There are various Synchronization Contexts used by .NET, the most important of which are the UI thread contexts used by WinForms and WPF."

  • "Instances of SynchronizationContext itself don’t do anything very useful, so all actual instances of it tend to be subclasses.

    It also has static members which let you read and control the current SynchronizationContext.

    The current SynchronizationContext is a property of the current thread.

    The idea is that at any point that you’re running in a special thread, you should be able to get the current SynchronizationContext and store it. Later, you can use it to run code back on the special thread you started on. All this should be possible without needing to know exactly which thread you started on, as long as you can use the SynchronizationContext, you can get back to it.

    The important method of SynchronizationContext is Post, which can make a delegate run in the right context"
    .

  • "Some SynchronizationContexts encapsulate a single thread, like the UI thread.
    Some encapsulate a particular kind of thread — for example, the thread pool — but can choose any of those threads to post the delegate to. Some don’t actually change which thread the code runs on, but are only used for monitoring, like the ASP.NET Synchronization Context"

沒落の蓅哖 2024-10-08 00:18:10

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

为什么不在表单的 Loaded 事件中设置 UIConfiguration.UIThread 或类似的内容?

I don't believe that every thread does have its own SynchronizationContext - it just has a thread-local SynchronizationContext.

Why don't you just set UIConfiguration.UIThread in the Loaded event of your form, or something similar?

丶情人眼里出诗心の 2024-10-08 00:18:10

用于从 ThreadExecutionContext 获取 SynchronizationContext 的完整且有效的扩展方法(如果不存在,则为 null) ,或来自 DispatcherDispatcherSynchronizationContext。在 .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 块预先初始化。这使我们能够巧妙地拦截第一个调用者,以便运行一次性初始化,这会准备一个小型且永久的替换委托,该委托速度更快且无反射。

勇敢的初始化工作的最后一步是将替换替换为“__get”,这同时也是悲剧性地意味着代码丢弃自身,不留下任何痕迹,并且所有后续调用者直接进入 DynamicMethod 正确的状态,而无需甚至有一点旁路逻辑。

static Func<Ectx, Sctx> __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<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>));

    // 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 字段可以通过传统反射(加上一些扩展方法磨砂)轻松而简单地解决。但我想我应该分享这种我个人已经使用了很长一段时间的方法,因为它也很容易适应并且同样广泛适用于此类情况。

当进入非公开领域需要极限性能时尤其合适。我想我最初在基于 QPC 的频率计数器中使用了它,其中在每 20 或 25 纳秒迭代一次的紧密循环中读取字段,这对于传统反射来说实际上是不可能的。

主要答案到此结束,但下面我包含了一些有趣的观点,与提问者的询问不太相关,更多的是与刚刚演示的技术相关。

 


运行时调用者

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

return (__get = (Func<Ectx, Sctx>)dm.CreateDel...(...))(arg);

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

ldarg.0<br />ldfld SynchronizationContext _syncContext/ExecutionContext<br />ret

无锁线程安全

我应该注意,函数体中的交换完全是考虑到 .NET 内存模型 和无锁原理的线程安全操作。后者倾向于以可能进行重复或冗余工作为代价来保证前进的进度。在完全健全的理论基础上正确允许多路竞赛初始化:

  • 竞赛入口点(初始化代码)是全局预配置和保护的(由.NET加载程序),以便(多个)竞赛者(如果有)进入相同的初始值设定项,永远不会被视为 null
  • 多个种族产品(吸气剂)在逻辑上总是相同的,因此任何特定赛车手(或后来的非赛车呼叫者)碰巧拿起哪一件并不重要,甚至任何赛车手最终是否使用他们自己生产的产品都无关紧要;
  • 每个安装交换都是大小为 IntPtr 的单个存储,保证对于任何相应平台位数都是原子的;
  • 最后,从技术上来说,对于完美的形式正确性来说绝对至关重要,“失败者”的工作产品由 GC 回收,因此不会泄漏。在这种类型的比赛中,失败者是除了最后一位< /em> 终结者(因为其他人的努力都被愉快地、简单地覆盖为相同的结果)。

尽管我相信这些要点结合起来可以充分保护在每种可能情况下编写的代码,但如果您仍然对总体结论持怀疑或警惕态度,那么您始终可以添加额外的防弹层:

var tmp = (Func<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>));
Thread.MemoryBarrier();
__get = tmp;
return tmp(arg);

这只是一个偏执版本。与早期的压缩单行代码一样,.NET 内存模型保证对“__get”的位置只有一次存储,以及零次提取。 (顶部的完整扩展示例确实执行了额外的主内存获取,但由于第二个要点,仍然是合理的)正如我所提到的,这些对于正确性来说都不是必需的,但从理论上讲,它可以给出一个微小的结果性能奖励:通过提前结束竞争,在极少数情况下,积极的刷新可以防止脏缓存行上的后续调用者进行不必要的(但同样是无害的)竞争。

双重 thunking

对最终超快方法的调用仍会thunked 通过前面所示的静态扩展方法。
这是因为我们还需要以某种方式表示编译时实际存在的入口点,以便编译器绑定并传播元数据。对于 IDE 中的强类型元数据和智能感知的巨大便利性来说,对于在运行时才能真正解析的自定义代码,双重 thunk 是一个很小的代价。然而,它的运行速度至少与静态编译代码一样快,方式比在每次调用时进行大量反射更快,因此我们可以两全其美!

Complete and working extension methods for getting the SynchronizationContext from a Thread or ExecutionContext (or null if none is present), or a DispatcherSynchronizationContext from a Dispatcher. Tested on .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 ... */
}

All of the above functions end up calling the __get code shown below, which warrants some explanation.

Note that __get is a static field, pre-initialized with a discardable lambda block. This allows us to neatly intercept the first caller only, in order to run the one-time initialization, which prepares a tiny and permanent replacement delegate that's much faster and reflection-free.

The final act for the intrepid initialization effort is to swap the replacement into '__get', which simultaneously and tragically means the code discards itself, leaving no trace, and all subsequent callers proceed directly into the DynamicMethod proper without even a hint of bypass logic.

static Func<Ectx, Sctx> __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<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>));

    // oh yeah, don't forget to handle the first caller's request
    return __get(arg);                 //  ...never to come back here again. SAD!
};

The cute part is the very end where--in order to actually fetch the value for the pre-empted first caller--the function ostensibly calls itself with its own argument, but avoids recursing by replacing itself immediately prior.

There's no particular reason for demonstrating this unusual technique on the particular problem of SynchronizationContext under discussion on this page. Grabbing the _syncContext field out of an ExecutionContext could be easily and trivially addressed with traditional reflection (plus some extension method frosting). But I thought I'd share this approach which I've personally used for quite a while now, because it is also easily adapted and just as widely applicable to such cases.

It's especially appropriate when extreme performance is necessary in accessing the non-public field. I think I originally used this in a QPC-based frequency counter where the field was read in a tight loop that iterated every 20 or 25 nanoseconds, which wouldn't really be possible with conventional reflection.

This concludes the main answer, but below I've included some interesting points, less relevant to the questioner's inquiry, moreso to the technique just demonstrated.

 


Runtime callers

For clarity, I separated the "installation swap" and "first usage" steps into two separate lines in the code shown above, as opposed to what I have in my own code (the following version also avoids one main-memory fetch versus the previous, potentially implicating thread-safety, see detailed discussion below):

return (__get = (Func<Ectx, Sctx>)dm.CreateDel...(...))(arg);

In other words, all callers, including the first, fetch the value in exactly the same way, and no reflection code is ever used to do so. It only writes the replacement getter. Courtesy of il-visualizer, we can see the body of that DynamicMethod in the debugger at runtime:

ldarg.0<br>ldfld SynchronizationContext _syncContext/ExecutionContext<br>ret

Lock-free thread safety

I should note that swapping in the function body is a fully thread-safe operation given the .NET memory model and the lock-free philosophy. The latter favors forward-progress guarantees at the possible expense of doing duplicate or redundant work. Multi-way racing to initialize is correctly permitted on a fully sound theoretical basis:

  • the race entry point (initialization code) is globally pre-configured and protected (by the .NET loader) so that (multiple) racers (if any) enter the same initializer, which can never be seen as null.
  • multiple race products (the getter) are always logically identical, so it doesn't matter which one any particular racer (or later non-racing caller) happens to pick up, or even whether any racer ends up using the one they themselves produced;
  • each installation swap is a single store of size IntPtr, which is guaranteed to be atomic for any respective platform bitness;
  • finally, and technically absolutely critical to perfect formal correctness, work products of the "losers" are reclaimed by GC and thus do no leak. In this type of race, losers are every racer except the last finisher (since everyone else's efforts get blithely and summarily overwritten with an identical result).

Although I believe these points combine to fully protect the code as written under every possible circumstance, if you're still suspicious or wary of the overall conclusion, you can always add an extra layer of bulletproofing:

var tmp = (Func<Ectx, Sctx>)dm.CreateDelegate(typeof(Func<Ectx, Sctx>));
Thread.MemoryBarrier();
__get = tmp;
return tmp(arg);

It's just a paraniod version. As with the earlier condensed one-liner, the .NET memory model guarantees that there is exactly one store--and zero fetches--to location of '__get'. (The full expanded example at the top does do an extra main memory fetch, but is still sound thanks to the second bullet point) As I mentioned, none of this should be necessary for correctness, but it could, in theory, give a miniscule performance bonus: by conclusively ending the race earlier, the aggressive flush could, in an extremely rare case, prevent a subsequent caller on a dirty cache line from unnecessarily (but again, harmlessly) racing.

Double-thunking

Calls into the final, hyper-fast method are still thunked through the static extension methods shown earlier.
This is because we also need to somehow represent entry point(s) that actually exist at compile time for the compiler to bind against and propagate metadata for. The double-thunk is a small price to pay for the overwhelming convenience of strongly-typed metadata and intellisense in the IDE for customized code that can't actually be resolved until runtime. Yet it runs at least as fast as statically compiled code, way faster that doing a bunch of reflection on every call, so we get the best of both worlds!

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文