【问题标题】:Listening for events in background thread在后台线程中监听事件
【发布时间】:2013-10-02 11:18:47
【问题描述】:

我想启动一个线程来监听事件并在后台处理它们。这是我到目前为止所做的:

private Thread mThread;
private bool mKeepHandlingIt;

private void Init()
{
    mKeepHandlingIt = true;
    mThread = new Thread(ProcessEvents);
    mThread.SetApartmentState(ApartmentState.STA);
    mThread.Start();
}

private void ProcessEvents()
{
    StaticClass.CoolEvent += HandleIt;
    StaticClass.StartDoingStuff();

    while (mKeepHandlingIt)
    {
        Application.DoEvents();
    }

    StaticClass.StopDoingStuff();
    StaticClass.CoolEvent -= HandleIt;
}

private void HandleIt(object sender, EventArgs e)
{
    //Handles it
}

protected override void Dispose()
{
    if (mThread != null)
    {
        mKeepHandlingIt = false;
        mThread.Join();
    }
}

有没有更好的方法来解决这个问题?像更适合此目的的线程类?在这种情况下,BackgroundWorker 似乎不是更好的选择...

但是,如果这是一个好方法,那么我就有一个我不太理解的问题。 上述解决方案适用于监听事件并处理它们,但是当调用 Dispose 并将 mKeepHandlingIt 设置为 false 时,退出 while 循环(如果我在循环中放置断点,调试器不会中断)但之后没有代码循环被执行并且 mThread.Join 永远不会返回。基本上......我想知道的是:我如何停止线程,然后确保在它清理之前不要继续?

【问题讨论】:

  • 我不会使用Application.DoEvents,因为它可能会导致启动额外的线程并发生奇怪的事情 - (假设用户单击一个按钮以启动一个进程,然后由于@987654323而再次单击它@ 再次启动该过程!)。由于您在单独的线程上运行东西,因此您不需要它(UI 线程不会阻塞)。如果您可以访问更高版本的 .NET,我会考虑使用 TPL/Task
  • HandleIt 的代码将在调用事件的同一线程上执行,而不是您创建的线程。
  • 运行一个线程来处理(所有?)事件是没有用的。无论如何,GUI 都需要自己的事件。
  • 啊,我实际上并没有阅读 Application.DoEvents 究竟做了什么,我只是在一个示例中看到它,有人试图做类似的事情并假设它做了我认为它做的事情......现在我发现在这里调用它是没有意义的。我应该只使用 Thread.Sleep(10) 还是其他东西?
  • 我现在有一个任务,它执行与 ProcessEvents 相同的代码,除了 while 循环如下所示: while (!token.IsCancellationRequested) { Thread.Sleep(10); }。我可以完美地启动和停止线程,它会按照我的意愿进行清理,但现在没有处理任何事件! :(

标签: c# multithreading events


【解决方案1】:

这段代码有很多个问题。首先,它受到事件处理程序在该工作线程上运行的错觉的影响。这不是事件的工作方式,处理程序在与触发事件的代码完全相同的线程上运行。 .NET 中没有让它跳到另一个线程的机制。

您可以在调试器的 Debug + Windows + Threads 窗口中看到一些内容。在 Init() 方法上设置断点,在事件处理程序上设置断点。当它们中断时,切换到该调试器屏幕并记下选定的线程。

使用 Application.DoEvents() 非常危险。但不是在这里,线程没有创建任何窗口,所以没有事件可做。相反,线程只会烧掉 100% 的核心,而根本不会完成任何事情。

mKeepHandlingIt 变量通常不会像您希望的那样做。当您在 32 位机器上运行代码的发布版本时,线程将永远看到它被设置为 false,因此线程将永远不会退出。并且 Thread.Join() 会死锁。这是由抖动优化器引起的,它将 bool 变量存储在 cpu 寄存器中,并且永远不会从内存中重新加载它。您必须声明变量 volatile 以防止进行此优化。这是一个 hack,您应该始终使用适当的同步对象来向线程发出信号,此处适合使用 AutoResetEvent。

删除这段代码,对你没有帮助。

【讨论】:

  • 嗯,我原来的问题是这样的:Init 的调用是由加载视图的 BackgroundWorker 进行的,而该视图又由主 UI 线程启动,但是当 BackgroundWorker 完成时,什么都没有留下来监听事件...如果我订阅了 BackgroundWorker 线程中的事件,然后启动一个新线程,该线程只使用 Thread.Sleep(1) 或类似的东西循环,然后停止线程并取消订阅事件稍后在第三个线程中,这会起作用吗?或者监听事件的线程是否必须自己订阅和取消订阅?
  • 您还没有得出事件处理程序实际上并未在线程上运行的结论。除非你使用我给你的调试提示,否则你无法到达那里。
  • 嗯,对于我来说,代码最终在哪个线程上运行完全无关紧要(我的处理程序必须使用 Dispatcher.Invoke 来执行它的代码),我只想让它运行。我明白你在说什么,引发事件的线程也执行处理程序代码。我感到困惑的是:我订阅/取消订阅事件的线程是否重要?如果不是,为什么线程需要处于活动状态才能触发处理程序?我对所有这些线程业务还是很陌生......你现在可能已经意识到了。
  • 不,没关系。您正在询问有关我看不到的代码的问题,完全不清楚是什么代码实际触发了该事件。使用调试器获得洞察力。
  • 该事件是由我没有源代码的第三方库触发的......但是,你是说我实际上根本不需要这个线程吗?我是否应该能够订阅 BackgroundWorker 中的事件,然后当 BackgroundWorker 完成并死亡时,事件仍应由触发事件的线程中的处理程序方法处理?
【解决方案2】:

对于大多数 情况,您永远不应该使用 doevents。 s。

例如看:

如果没有更多线程可用,使用新线程创建线程可能会遇到麻烦。因此您可以使用Threadpool

但我会stronlgy建议看看TasksTPL

那你就可以开始了

Task.Factory.StartNew()

当你使用异步库并且你有像continuewith这样的函数时,还有等待。

另见MSDN await one ore more tasks

            // Wait for all tasks to complete.
            Task[] tasks = new Task[10];
            for (int i = 0; i < 10; i++)
            {
                tasks[i] = Task.Factory.StartNew(() => DoSomeWork(10000000));
            }
            Task.WaitAll(tasks);

Async 和 await 也不一定使用线程,而是异步工作,请参阅:async-await-vs-threads

这对安全资源来说是一个很大的好处。尤其是当你有 io 操作时。

还有一种更复杂的方法是使用某种服务总线在单独的过程中实现监听(无论您想要实现什么)

【讨论】:

  • 带有 CancellationToken 的任务听起来正是我所需要的。我会试试...
猜你喜欢
  • 2016-09-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-05-17
  • 1970-01-01
  • 1970-01-01
  • 2017-07-26
  • 2016-02-06
相关资源
最近更新 更多