代码之家  ›  专栏  ›  技术社区  ›  chiccodoro

从给定线程获取SynchronizationContext

  •  7
  • chiccodoro  · 技术社区  · 15 年前

    我好像找不到 SynchronizationContext 一个给定的 Thread :

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

    因为我需要从整个前端应用程序的不同位置发布到UIThread。所以我在一个名为 UIConfiguration . 我把这个财产放在 Program.Main

    UIConfiguration.UIThread = Thread.CurrentThread;
    

    在那一刻,我可以确定我有正确的线程,但是我不能设置一个静态属性,比如

    UIConfiguration.SynchronizationContext = SynchronizationContext.Current
    

    螺纹 反对,还是我完全错了?

    5 回复  |  直到 15 年前
        1
  •  13
  •   Reed Copsey    15 年前

    这是不可能的。问题是 SynchronizationContext Thread 是两个完全不同的概念。

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

    也有可能 成为 . 同步上下文可以很容易地设置为与外部服务或整个线程池等进行同步。

    就你而言,我建议你 UIConfiguration.SynchronizationContext 在初始的主窗体的加载事件中。上下文保证在该点启动,并且在任何情况下都将在消息泵启动之前不可用。

        2
  •  7
  •   Javert93    15 年前

    我知道这是一个老问题,并为亡灵道歉,但我只是找到了一个解决这个问题的方法,我想可能对我们这些人有帮助,谁一直在谷歌这个(它不需要一个控制实例)。

    基本上,您可以创建WindowsFormsSynchronizationContext的实例,并在 Main

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

    我已经在我的申请中做到了这一点,而且它的工作非常完美,没有任何问题。但是,我应该指出 主要 是用STAThread标记的,所以我不确定这是否仍然有效(或者是否有必要),如果 而是用MTAThread标记。

    _UISyncContext 已在中的模块级别定义 Program

        3
  •  6
  •   Gennady Vanin Геннадий Ванин Mikael Svenson    13 年前

    我发现亚历克斯·戴维斯的《C#5.0中的异步》(Async in C#5.0)一书中的以下段落对我最简洁、最有帮助。O'Reilly Publ.,2012年”,第48-49页:

    • " SynchronizationContext is a class provided by the .NET Framework ,它可以在 .
      .NET使用了各种同步上下文,其中最重要的是WinForms和WPF使用的UI线程上下文。”

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

      它还具有静态成员,允许您读取和控制当前 行绪 .

      电流 行绪 是当前线程的属性。

      这个想法是,在任何一点上,你运行在一个特殊的线程中,你应该能够得到当前 储存起来。稍后,您可以使用它在启动的特殊线程上运行代码。所有这些都应该是可能的 .

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

    • " Some SynchronizationContexts encapsulate a single thread, like the UI thread .
      一种特殊的线 将委托发布到 . 有些线程实际上不会更改代码运行在哪个线程上,但仅用于监视,如ASP.NET同步上下文。”

        4
  •  2
  •   Jon Skeet    15 年前

    我不相信每根线 有自己的 SynchronizationContext -它只是有一个本地线程 行绪 .

    UIConfiguration.UIThread Loaded 你的形式,或类似的事情?

        5
  •  2
  •   Glenn Slayden    9 年前

    获取 SynchronizationContext 从一个 Thread ExecutionContext null 如果不存在),或 DispatcherSynchronizationContext 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

    请注意 是一个静态字段,使用可丢弃的lambda块预先初始化。这样我们就可以巧妙地拦截第一个来电者 ,以便运行一次性初始化,这将准备一个小而永久的替换委托,该替换委托速度更快且无反射。

    无畏的初始化工作的最后一步是将替换换成'\uu 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!
    };
    

    可爱的部分就是这样的结尾:为了实际获取被抢占的第一个调用方的值,函数表面上用自己的参数调用自己,但通过替换前面的参数来避免递归。

    没有什么特别的理由来证明这种不寻常的技术在 行绪 本页讨论中。抓住 _syncContext 外场 执行上下文 可以用传统反射(加上一些扩展方法磨砂)简单地解决。但我想我会分享我个人已经使用了很长一段时间的这种方法,因为它也很容易适应,同样广泛地适用于这种情况。

    这是主要答案的结论,但在下面我包含了一些有趣的观点,与提问者的提问不太相关,更与刚才演示的技巧有关。


    运行时调用程序

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

    换句话说,所有来电者, 包括第一个 getter . 承蒙 il-visualizer 动态方法 在运行时的调试器中:

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

    我应该注意,在函数体中交换是一个完全线程安全的操作 memory model 以及无锁哲学。后者倾向于以可能的重复或冗余工作为代价的远期进度保证。在完全可靠的理论基础上,允许进行多路赛车初始化:

    • 无效的
    • 多个种族产品(getter)在逻辑上总是相同的,所以不管哪个特定的赛车手(或后来的非赛车的呼叫者)碰巧接上了,甚至不管哪个赛车手最终是否使用了他们自己生产的赛车;
    • 每个安装交换都是一个大小为 IntPtr
    • 最后,技术上 对完善形式正确性至关重要 ,失败者的工作产品 GC 这样就不会泄漏。在 this type of race 最后的 终结者(因为其他人的努力都会被一个相同的结果轻松地、草率地覆盖)。

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

    这只是一个偏执的版本。与前面压缩的一行代码一样,.NET内存模型保证只有一行代码 --和零 获取 早期的 ,在极少数情况下,主动刷新可以防止脏缓存线上的后续调用方不必要地(但同样无害地)进行竞争。

    重击

    调用final的超快速方法仍然是 thunked 通过前面显示的静态扩展方法。 这是因为我们还需要以某种方式表示编译时实际存在的入口点,以便编译器与元数据绑定并传播元数据。double-thunk是为IDE中的强类型元数据和intellisense所带来的巨大便利而付出的一个小代价,这些自定义代码在运行时才能真正解析。但它的运行速度至少和静态编译的代码一样快, 方式 更快地在每次通话中都做一系列的思考,这样我们就能两全其美!