如何使后台工作线程之间的事件在自己的上下文中执行

Posted

技术标签:

【中文标题】如何使后台工作线程之间的事件在自己的上下文中执行【英文标题】:How to make events between backgroundworker threads execute in their own context 【发布时间】:2021-06-22 13:34:02 【问题描述】:

这个问题是 11 年前提出的,但答案都假设为 Winform/WPF。我想再问一次,但请澄清这是两个后台工作线程。

我想要两个线程。让我们称呼他们:

线程 A(Thread.CurrentThread.ManagedThreadId 为 16) 线程 B(Thread.CurrentThread.ManagedThreadId 为 18);

线程 A 触发一个事件,线程 B 监听这个事件。当线程 B 事件侦听器(委托)被执行时,它与线程 A 的上下文 (16) 一起执行。

我想做的是能够在线程 A (16) 中触发事件,并且委托代码在线程 B (18) 的上下文中执行。

据我所知,在这种情况下没有 SynchronizationContext 可用。

我该怎么做?

感谢您的帮助。

【问题讨论】:

你有什么样的应用?控制台应用程序? 这是一个运行多个插件的windows服务。有一个线程守护进程订阅来自其他组件的事件。我有一个错误,其中一个组件由于委托中的错误而死亡(它永远不会退出)。这让我觉得很奇怪,因为这相当于线程 B 中的错误导致线程 A 退出。因此,从错误处理的角度来看,我更喜欢在守护进程上下文中运行的线程守护进程委托方法(线程 B #18)。 没有“线程 B 事件监听器”。事件侦听器可能已被线程 B 附加,但没有任何东西记得。它只是一个事件监听器,它运行在引发事件的任何线程上。 @Enigmativity:问题是如何让触发事件的线程编组数据并在另一个线程的上下文(线程 b)中执行。我真正在寻找的是我在 WinForms 中经常使用的等效模式: if (InvokeRequired) Invoke(() => MyEvent(myData));除了没有 SynchronizationContext 将调用从一个线程编组到 2 个工作线程之间的另一个线程。 【参考方案1】:

您可以考虑使用 Microsoft 的反应式框架(又名 Rx)- NuGet System.Reactive 并添加 using System.Reactive.Linq; - 这样您就可以利用 EventLoopScheduler 类。此类创建一个线程,您可以通过多种方式在该线程上安排任务。

鉴于private EventLoopScheduler _eventLoopScheduler = new EventLoopScheduler();,我可以这样做以在特定线程上运行代码:

IDisposable scheduled =
    _eventLoopScheduler
        .Schedule(() =>
        
            /* Run code in the `EventLoopScheduler` */
        );

您甚至可以安排未来的活动并使其重复发生。要取消预定代码,您可以致电scheduled.Dispose()

现在,鉴于private event EventHandler MyEvent;,我可以使用 Rx 库做到这一点:

IDisposable subscription =
    Observable
        .FromEventPattern<EventHandler, EventArgs>(
            h => this.MyEvent += h,
            h => this.MyEvent -= h)
        .ObserveOn(_eventLoopScheduler)
        .Subscribe(x =>
        
            /* Run code in the `EventLoopScheduler` */
        );

这订阅了事件并将执行推送到EventLoopScheduler创建的线程。

完成后调用subscription.Dispose()_eventLoopScheduler.Dispose() 进行清理并让线程结束。

【讨论】:

我最终做了类似的事情。我在我的线程守护程序中定义了一个 Queue ,它接收来自其他线程的事件。 OnDoWork 定义了一个 while 循环,它 deuques 并调用操作,然后等待锁。它还定义了一个 Invoke 方法,该方法获取相同的锁,将一个动作排入队列,并脉冲锁。这有效地将线程 A 的动作编组到线程 B 的执行上下文。但是,我更希望能够返回 IAsyncResult,以便线程 A 可以有效地 JOIN 以从线程 B 获取事件委托的返回值。 我通过将包含数据、委托和锁的对象加入队列来解决 IAsyncResult 问题。与以前相同的想法,但不是仅编组由另一个线程 (B) 调用的动作,而是发送允许同步的对象。所以调用者 (A) 创建 IAsyncResult 对象,传递给事件,事件入队对象 (InvokeRequired),脉冲消息锁,然后返回。现在我们可以选择加入 (EndInvoke) 以获取线程 A 中的结果。同时,线程 B 唤醒、出列对象、调用操作、设置结果、设置锁。线程A被唤醒,可以从object获取返回结果。 @codinglifestyle - 请将您的答案作为答案发布。【参考方案2】:

像往常一样,当我找到一个问题的优雅解决方案时,我会看着它,想知道为什么它没有那样从我的脑海中倾泻而出?遗憾的是,我花了很长时间才到达那里,但我对结果以及如何不费吹灰之力地使其可重复使用感到满意。

我希望这是一个有用且有趣的阅读:

https://codinglifestyle.wordpress.com/2021/04/15/thread-synchronization-between-worker-threads/

干杯, 克里斯

【讨论】:

以上是关于如何使后台工作线程之间的事件在自己的上下文中执行的主要内容,如果未能解决你的问题,请参考以下文章

挂钩事件 Outlook VSTO 在主线程上继续工作

如何使 Backgroundworker 中的进程在 Cancellation Pending 事件中退出

线程应用程序上不同原语的 IPC 延迟

PySide2:如何使装饰槽在其工作线程上执行?

CoreData - 如何使对象在不同的​​线程/上下文中保持最新?

为啥我的 QThread 正在使主线程挨饿?