【问题标题】:Main thread waits for multiple backgroundworker threads to complete主线程等待多个后台工作线程完成
【发布时间】:2014-03-07 13:32:19
【问题描述】:

我生成了多个后台工作线程,并希望我的主线程等到所有线程都完成。解决方案是每次生成后台工作线程时向列表中添加一个项目,并在 RunWorkerCompleted 中删除它们。但是,如何将列表作为参数传递给 RunWorkerCompleted?

FuncA()
{
 foreach()
 {
    /* add an item to the list */
   _bw.RunWorkerAsync();
 }
 m_event.WaitOne(); /* Main thread waits here */
}
static bw_DoWork()
{

}
static bw_RunWorkerCompleted()
{ 
    /* delete item from list */
    /* if list is empty signal m_event.Set() */
}

【问题讨论】:

  • 你不想阻塞主线程。这将冻结你的用户界面。另外,顺便说一句,它会阻止RunWorkerCompleted 被触发。
  • 你能解释一下如果主线程被阻塞,RunWorkerCompleted 是如何被触发的吗?
  • BGW 会将完成的事件编组到主线程,主线程无法处理请求,因为它被阻塞了,直到当前请求完成。在 BGW 完成事件运行之前,它不会完成。死锁。
  • 感谢服务。死锁问题是否仅与后台工作线程有关?
  • 死锁问题是因为您阻塞了 UI 线程。不要那样做。您不想阻塞 UI 线程。如果您想在所有后台任务完成时(在 UI 线程中)简单地做某事,您可以这样做。我的回答向您展示了一种方法(我认为最简单的方法)。

标签: c# multithreading


【解决方案1】:

将 TPL 与 await 一起使用使这非常变得容易:

private void someEventHandler()
{
    var results = await Task.WhenAll(
        Task.Run(() => ComputeSomeValue()),
        Task.Run(() => ComputeSomeOtherValue()),
        Task.Run(() => ComputeYetAnotherValue()));
    DoSomethingWithResults(results);
}

对于 .NET 4.0 解决方案,您可以在不使用 await 的情况下使用任务:

private void someEventHandler()
{
    Task.Factory.ContinueWhenAll(new[]{
        Task.Run(() => ComputeSomeValue()),
        Task.Run(() => ComputeSomeOtherValue()),
        Task.Run(() => ComputeYetAnotherValue())}
        , resultTask => DoSomethingWithResults(resultTask.Result);
}

【讨论】:

  • 不幸的是,我的应用程序在 .NET 框架 4.0 中运行并且不支持 TPL :(。由于其他依赖关系限制,我也无法更改框架。还有其他解决方法吗?
  • 当然,还有其他方法,尽管它们没有那么好。我添加了一种可能性。
【解决方案2】:

当调用 _bw.RunWorkerAsync() 时,您可以将列表作为参数传递。 RunWorkerAsync 方法有一个以对象类型为参数的重载方法。

您可以在 bw_DoWork 事件结束时从列表中删除项目,而不是在 bw_RunWorkerCompleted 事件中。后者应该在更新某些 UI 元素时使用。

List<object> list = new List<object>();
FuncA()
{
    foreach()
    {
        /* add an item to the list */
       _bw.RunWorkerAsync(list);
     }
    m_event.WaitOne(); /* Main thread waits here */
}
static bw_DoWork()
{
    // Do the stuff.

    /* delete item from list */
    var list = e.Argument as List<object>;
    /* if list is empty signal m_event.Set() */
}

【讨论】:

  • 这会在长时间运行的工作期间阻塞 UI 线程,从而冻结整个 UI。不应该这样做;在此工作进行期间,应让 UI 线程继续处理消息。
【解决方案3】:

如果您使用的是 .NET Framework 4 或更高版本,您可以使用 System.Threading.CountdownEvent。 在 FuncA() 中,在启动后台工作程序之前,您必须使用一些对象初始化 CountdownEvent,并且在 WorkerCompleted 处理程序中,您应该调用 CountdownEvent 的 Signal() 方法。 在所有工人完成之前不应继续的线程中,您必须调用 CountdownEvent 的 Wait() 方法,然后在该方法之后调用您必须稍后执行的所有代码。

【讨论】:

  • 这会死锁。您将在等待后台工作人员时阻塞 UI 线程,并且RunWorkerCompleted 事件将无法运行,因为它将处理程序编组到 UI 线程。由于每个人都在等待对方,因此您会陷入僵局。
  • @Servy 这取决于您在哪里等待 CountdownEvent。对我来说,您应该从单独的线程更新 UI 并因此在该线程中等待完成似乎很清楚。我写的不是需要粘贴到项目中的确切代码,而只是一个想法应该如何实现!
  • 您的回答特别提到在FuncA 中等待,很明显它似乎在 UI 线程中运行。创建一个全新的线程只是为了等待所有这些工作人员完成而什么都不做也是完全不合适的。你根本不需要线程来做到这一点。
  • 真的吗??? “在所有工作人员完成之前不应继续的线程中” - 我说的是应该在 FuncA() 中调用 Wait 吗?谁说线程除了等待什么都不做?这是你的话,不是我的。
  • OP 说他正在寻找一种在主线程中等待的方法。您是说他应该使用CountdowntEvent 在不应继续的线程中等待,鉴于所提出的问题,是UI 线程。是的,我的话是你不应该创建一个新线程,这样它就什么都不做,只能等待其他事情完成。它的设计很差,浪费系统资源,还有更​​好的选择。
猜你喜欢
  • 1970-01-01
  • 2017-05-15
  • 1970-01-01
  • 1970-01-01
  • 2020-09-21
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多