简体   繁体   English

从给定的线程获取SynchronizationContext

[英]Get SynchronizationContext from a given Thread

I seem not to find how to get the SynchronizationContext of a given Thread : 我似乎没有找到如何获取给定ThreadSynchronizationContext

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

Why should I need that? 我为什么需要那个?

Because I need to post to the UIThread from different spots all across the front end application. 因为我需要从前端应用程序的不同位置发布到UIThread。 So I defined a static property in a class called UIConfiguration . 所以我在名为UIConfiguration的类中定义了一个静态属性。 I set this property in the Program.Main method: 我在Program.Main方法中设置了这个属性:

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

This is not possible. 这是不可能的。 The problem is that a SynchronizationContext and a Thread are really two completely separate concepts. 问题是SynchronizationContextThread实际上是两个完全独立的概念。

While it's true that Windows Forms and WPF both setup a SynchronizationContext for the main thread, most other threads do not. 虽然Windows Forms和WPF都为主线程设置了SynchronizationContext ,但大多数其他线程都没有。 For example, none of the threads in the ThreadPool contain their own SynchronizationContext (unless, of course, you install your own). 例如,ThreadPool中没有任何线程包含自己的SynchronizationContext(当然,除非您自己安装)。

It's also possible for a SynchronizationContext to be completely unrelated to threads and threading . SynchronizationContext也可能与线程和线程完全无关 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. 在您的情况下,我建议您在初始的主窗体的Loaded事件中设置UIConfiguration.SynchronizationContext The context is guaranteed to be started at that point, and will be unusable until the message pump has been started in any case. 保证在此时启动上下文,并且在任何情况下都将启动消息泵之前将无法使用该上下文。

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). 我知道这是一个老问题,并为necro道歉,但我刚刚找到了解决这个问题的方法,我认为这对我们这些一直在谷歌搜索的人来说可能是有用的(并且它不需要一个Control实例)。

Basically, you can create an instance of a WindowsFormsSynchronizationContext and set the context manually in your Main function, like so: 基本上,您可以创建WindowsFormsSynchronizationContext的实例并在Main函数中手动设置上下文,如下所示:

    _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. 但是,我应该指出我的Main标有STAThread,所以如果你的Main标记为MTAThread,我不确定它是否仍然有用(或者如果它甚至是必要的话)。

EDIT: I forgot to mention it, but _UISyncContext is already defined at the module level in the Program class in my application. 编辑:我忘了提及它,但_UISyncContext已经在我的应用程序的Program类的模块级别定义。

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: 我发现最简洁,最有帮助的是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 . SynchronizationContext是.NET Framework提供的一个类 ,它能够在特定类型的线程中运行代码。
    There are various Synchronization Contexts used by .NET, the most important of which are the UI thread contexts used by WinForms and WPF." .NET使用各种同步上下文,其中最重要的是WinForms和WPF使用的UI线程上下文。“

  • "Instances of SynchronizationContext itself don't do anything very useful, so all actual instances of it tend to be subclasses. SynchronizationContext本身的实例本身没有做任何非常有用的事情,因此它的所有实际实例都倾向于是子类。

    It also has static members which let you read and control the current SynchronizationContext . 它还有静态成员,可以让您读取和控制当前的SynchronizationContext

    The current SynchronizationContext is a property of the current thread. 当前的SynchronizationContext是当前线程的属性。

    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. 这个想法是,在任何一个你在特殊线程中运行的时候,你应该能够获得当前的SynchronizationContext并存储它。 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 . 所有这一切都应该是可能的, 而不需要确切地知道你开始使用哪个线程,只要你可以使用SynchronizationContext,你就可以回到它

    The important method of SynchronizationContext is Post , which can make a delegate run in the right context" SynchronizationContext的重要方法是Post ,它可以使委托在正确的上下文中运行“
    .

  • " Some SynchronizationContexts encapsulate a single thread, like the UI thread . 有些SynchronizationContexts封装了一个线程,就像UI线程一样
    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" 有些实际上并没有更改代码运行的线程,但仅用于监视,如ASP.NET同步上下文“

I don't believe that every thread does have its own SynchronizationContext - it just has a thread-local SynchronizationContext . 我不相信,每个线程确实有其自身的SynchronizationContext -它只是有一个线程本地SynchronizationContext

Why don't you just set UIConfiguration.UIThread in the Loaded event of your form, or something similar? 为什么不在UIConfiguration.UIThreadLoaded事件中设置UIConfiguration.UIThread ,或类似的东西?

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 . 完成和工作的扩展方法获得SynchronizationContext从一个ThreadExecutionContext (或null ,如果两者都不存在),或者DispatcherSynchronizationContextDispatcher Tested on .NET 4.6.2 . .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. 所有上述函数最终都会调用下面显示的__get代码,这需要一些解释。

Note that __get is a static field, pre-initialized with a discardable lambda block. 请注意, __get是一个静态字段,使用可丢弃的lambda块进行预初始化。 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. 无畏初始化工作的最后一个动作是将替换替换为'__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!
};

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. 没有特别的理由在本页讨论的SynchronizationContext的特定问题上演示这种不寻常的技术。 Grabbing the _syncContext field out of an ExecutionContext could be easily and trivially addressed with traditional reflection (plus some extension method frosting). ExecutionContext _syncContext字段可以通过传统反射(加上一些扩展方法结霜)轻松而简单地解决。 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. 我想我最初在一个基于QPC的频率计数器中使用它,在一个紧密的循环中读取场,每20或25纳秒迭代一次,这对于传统的反射是不可能的。

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 . 它只写替换的getter Courtesy of il-visualizer , we can see the body of that DynamicMethod in the debugger at runtime: 感谢il-visualizer ,我们可以在运行时在调试器中看到DynamicMethod的主体:

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. 我应该注意,在.NET 内存模型和无锁理念下,交换函数体是一个完全线程安全的操作。 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 . 竞争入口点(初始化代码)是全局预配置和保护的(由.NET加载器),以便(多个)竞争者(如果有的话)进入相同的初始化程序,永远不会被视为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; 每个安装交换是一个大小为IntPtr的单个存储,对于任何相应的平台位数,它保证是原子的;
  • finally, and technically absolutely critical to perfect formal correctness , work products of the "losers" are reclaimed by GC and thus do no leak. 最后,技术上对于完善形式正确性至关重要 ,“失败者”的工作产品由GC回收,因此不会泄漏。 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. 这只是一个paraniod版本。 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'. 与早期的浓缩单行程一样,.NET内存模型保证只有一个存储 - 零读取 - 到'__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. 对于自定义代码的强大类型元数据和智能感知的压倒性便利,双thunk是一个很小的代价,直到运行时才能实际解析。 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! 然而,它运行速度至少为静态编译的代码,更快的方法是在每次调用做了一堆反射的,所以我们得到两全其美的!

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 通过SynchronizationContext将超链接从其他线程添加到TextBlock - Add Hyperlink to TextBlock from other Thread by SynchronizationContext 从库中捕获主线程SynchronizationContext或Dispatcher - Capturing the main thread SynchronizationContext or Dispatcher from a library 线程中的C#线程:如何获取SynchronizationContext.Current? - C# Thread in Thread: how to get SynchronizationContext.Current? 这本书中的短语“当前同步文本是当前线程的属性是否正确”? - Is the phrase from a book “The current SynchronizationContext is a property of the current thread” correct"? SynchronizationContext Send()应该是同一个线程吗? - SynchronizationContext Send() supposed to be the same thread? 从另一个线程调用方法而不会阻塞该线程(或为非UI线程编写自定义SynchronizationContext)C# - call method from another thread without blocking the thread (or write custom SynchronizationContext for non-UI thread) C# 是否应该使用Invoke或SynchronizationContext从另一个线程更新表单控件? - Should I use Invoke or SynchronizationContext to update form controls from another thread? 如何获取使用SynchronizationContext的任务? 那么SynchronizationContext是如何使用的呢? - How to get a Task that uses SynchronizationContext? And how are SynchronizationContext used anyway? 强制 SynchronizationContext 发布到控制台应用程序中的主线程 - Force SynchronizationContext to post to main thread in console app 获取TaskScheduler / SynchronizationContext以在特定线程上执行 - Getting a TaskScheduler/SynchronizationContext to execute on a specific thread
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM