【问题标题】:Get EventHandler to call Invoke() on original thread获取 EventHandler 以在原始线程上调用 Invoke()
【发布时间】:2021-10-18 08:51:23
【问题描述】:

我正在使用System.Threading.Timer 异步重复某个任务。当任务准备就绪时,应该执行原始线程(调用Start())上的一个方法。

public event EventHandler StatusChanged;

public void Start()
{
    StatusChanged += new EventHandler(SomethingChangedMethod);
    new Timer(Pending, null, 0, 1000);
}

private void Pending(object state)
{
    //Do Something
    StatusChanged?.Invoke(this, EventArgs.Empty);
}

由于我不在控制或其他东西上,我不能打电话给Invoke()BeginInvoke()

【问题讨论】:

  • 原线程是什么线程?它是 UI 线程、ThreadPool 线程,还是使用 Thread 构造函数手动启动的?
  • 由于可以从库外部调用 Start(),因此我无法真正控制它。
  • 或者是用户必须在他的“SomethingChangedMethod”中关心的事情(例如,在控件上使用“BeginInvoke()”)
  • 附带说明,如果我站在你的立场上,我会非常担心允许未知代码在周期性Timer 的回调上运行。该组件不提供针对回调重叠调用的保护。不仅未知代码可能无法为并发做好准备,而且您最终可能会遇到越来越多的并发执行,从而导致资源耗尽和ThreadPool 饥饿。您可以查看this 问题以获取解决方案。
  • 是的,行得通。如果我使用 awaited 方法的返回(在我的情况下只是一个布尔值),如果需要,我可以直接从 Start() 调用 Invoke()。这也解决了正确线程的初始问题。再次感谢您的帮助。

标签: c# multithreading asynchronous event-handling


【解决方案1】:

由于可以从库外部调用 Start(),因此我无法真正控制它。

作为图书馆作者,您不知道什么是正确的做法。就你所知,原始线程刚刚启动以调用Start 并在很久以前退出。你不知道返回“相同”“线程”是正确的行为,所以当然,是的,你把它留给你的消费者。

同样,作为库作者,您不应该在选择使用什么日志框架、是否以及如何向用户显示错误消息(如果有用户)等方面做出选择。

做简单的事情,提出你的事件,让为应用程序挑选框架的人做出正确的选择。因为您不应该(不一定会损害您图书馆的潜在消费者)

【讨论】:

  • 感谢您提供的信息。所以必须在文档中告诉消费者,可以从不同的线程调用“StatusChanged”事件。我会接受另一个答案,因为它处理原始问题。
  • 我大体上同意,但值得注意的是,运行时中有一些特定情况确实捕获调用者的 SynchronizationContext,然后发回给它,例如BackgroundWorkerProgress<T>。通常,尽管在您的文档中明确说明这一点。在 ThreadPool 线程上引发异步事件是一种常见的做法,因此调用者可能会期待这一点,但调用它并没有什么坏处。如果你确实捕获了 SynchronizationContext,那么也要记录下来。
【解决方案2】:

首先,您可能会也可能不会向名为Start 的线程发布消息:只有当该线程具有与之关联的消息队列并且正在检查该线程时,您才能执行此操作队列。 UI线程这样做,但是例如线程池线程或您使用new Thread() 创建的线程不会。

如果线程有与之关联的消息队列,通常的做法是在该线程上安装SynchronizationContext。您可以使用SynchronizationContext.Current 获取线程的SynchronizationContext,将其存储起来,然后使用SynchronizationContext 将消息发布到该线程的消息队列。如果线程上没有安装SynchronizationContext,则SynchronizationContext.Current 返回null

public event EventHandler StatusChanged;
private Timer timer;
private SynchronizationContext synchronizationContext;

public void Start()
{
    synchronizationContext = SynchronizationContext.Current;
    StatusChanged += new EventHandler(SomethingChangedMethod);
    timer = new Timer(Pending, null, 0, 1000);
}

private void Pending(object state)
{
    // Do Something
    if (synchronizationContext != null)
    {
        synchronizationContext.Post(_ => StatusChanged?.Invoke(this, EventArgs.Empty), null);
    } 
    else
    {
        StatusChanged?.Invoke(this, EventArgs.Empty);
    }
}

【讨论】:

  • 谢谢,我会试试的。这是否也意味着如果线程没有消息队列,这通常是不可能的
  • 正确。如果一个线程没有检查消息队列,就没有办法告诉它做某事。尤其重要的是,因为如果它不检查消息队列,则根据定义,它正忙于做其他事情,并且没有办法直接跳入线程并强制它切换到做其他事情
猜你喜欢
  • 1970-01-01
  • 2012-04-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-07-06
  • 2018-11-18
  • 1970-01-01
  • 2023-04-01
相关资源
最近更新 更多