【问题标题】:Wait for ANY thread to finish, not ALL等待任何线程完成,而不是全部
【发布时间】:2013-12-13 02:00:01
【问题描述】:

我正在启动多个线程,想知道 any 什么时候结束。我知道以下代码:

foreach (Thread t in threads)
    t.Join();

但它只会等待所有个线程在一起。那太晚了。我需要知道一个线程何时完成,即使其他线程仍在运行。我正在寻找与WaitAny 等效的东西,仅用于线程。但我无法向我正在监视的所有线程添加代码,因此不能选择使用信号或其他同步对象。

一些澄清:我正在开发一个记录应用程序活动的记录/跟踪工具。我可以在线程启动时插入日志语句,但我不能在线程外的所有可能方式上插入日志语句(多个退出点、异常等)。所以我想注册新线程,然后在它完成写入日志条目时收到通知。我可以在每个线程上异步Join,但这意味着每个受监控线程都有第二个线程,这可能看起来有点太多开销。线程通过多种方式使用,无论是BackgroundWorkerTask 还是池线程。从本质上讲,它是一个线程,我想知道它什么时候完成。确切的线程机制由应用程序定义,而不是日志记录解决方案。

【问题讨论】:

  • 这是代码异味。您不会知道哪个线程完成了,因此您无法推断实际完成了什么工作。
  • 我添加了更多关于我正在尝试做的事情的背景信息。当然我需要知道哪个线程已经结束了,否则在这种情况下就没用了。
  • 我和汉斯一起做这个。为什么你的线程代码有多个退出点?在所有线程代码周围放置一个 try/catch,如果你想在中间的某个地方退出,则抛出。现有答案涵盖了我所有其他疑问 - 为什么您不断创建/终止/销毁线程,而不是使用刚刚收到信号的池或应用程序生命周期线程?
  • 再一次,我无法控制应用程序对线程的操作。我的目标只是提供一个日志记录解决方案,该解决方案将在线程完成时写入日志条目。如果我可以完全控制要记录的内容,那么我当然可以按照您的建议进行操作。

标签: c# multithreading synchronization


【解决方案1】:

使用Tasks 代替线程。它有WaitAny的方法。

Task.WaitAny

如你所见here

  • 更高效、更可扩展地使用系统资源。
  • 比线程或工作项更多的编程控制。

【讨论】:

  • 请参阅我的澄清编辑。我只有Thread.Current,但是它已经启动了。
【解决方案2】:

在我看来,WaitHandle.WaitAny 是最好的解决方案,因为你不喜欢使用它,因为某些 xyz 原因你可以尝试这样的事情。

利用Thread.Join(int) 方法的优势,该方法采用millisecond timeout 并在线程终止时返回true 或在超时时返回false

List<Thread> threads = new List<Thread>();

while (!threads.Any(x=> x.Join(100)))
{

}

如果您知道需要多长时间,您可以更改Join 的超时时间。

【讨论】:

  • 我以为会提出这个(忘了提了)。我只是担心监视线程上的持续活动可能会增加功耗。为了获得体面的时间分辨率,我认为我需要不超过 100 毫秒。
  • 那么,这是回答您的问题还是您正在寻找其他东西
  • 你应该检查 IsAlive ... Join 只会在他们每个人上调用一个 Join。此外,您应该在每次迭代中让线程进入睡眠状态,否则您将陷入困境。
  • 我认为 Join(100) 不会导致内部旋转。我认为 sleep 在这里无关紧要,因为 Join 类似于等待,它不会很昂贵
  • 1) 这是一个忙碌的等待,从概念上讲,这是一个非常糟糕的主意,应尽可能避免。 2)您不是每个循环“睡”了 100 毫秒,而是每个循环每个线程睡了 100 毫秒。如果循环中有 10 个线程,那么检查每个线程需要整整一秒钟。 (这是最低限度的,当然这实际上不会发生。它可能需要明显更长的时间。)在其中一个线程停止继续运行后等待这么长时间很可能是有问题的。
【解决方案3】:

我的回答是基于您的说明,即您所拥有的只是Thread.Current免责声明:IMO,你想要做的是一个黑客,因此我的想法无论如何也是一个黑客

因此,使用反射来获取所需线程的本地 Win32 句柄集。您正在寻找Thread.GetNativeHandle 方法,即internal,因此您将其称为thread.GetType().InvokeMember("GetNativeHandle", BindingFlags.InvokeMethod | BindingFlags.Instance | BindingFlags.NonPublic, ...)。使用您选择的反射工具或Framework sources 了解更多信息。获得手柄后,继续使用以下选项之一:

  • 设置您自己的 SynchronizationContext 实现(派生自它)并使用 SynchronizationContext.WaitHelper(waitAll: false) 等待您的非托管句柄。

  • 使用原始 Win32 API,例如 WaitForMultipleObjectsCoWaitForMultipleObjects(取决于您是否需要发送消息)。

在单独的子线程或池线程上执行等待。

[已编辑]根据您的目标线程的执行环境,此 hack 可能不起作用,因为托管和非托管线程之间的一对一映射 is not guaranteed

可以确定正在执行托管线程代码的 Windows 线程并检索其句柄。但是,为此 Windows 线程调用 SetThreadAffinityMask 函数仍然没有意义,因为托管调度程序可以在另一个 Windows 线程中继续执行托管线程

然而,这似乎是一个implication,仅适用于自定义 CLR 主机。此外,control managed thread affinityThread.BeginThreadAffinityThread.EndThreadAffinity 似乎是可能的。

【讨论】:

    【解决方案4】:

    您可以为您的工作线程使用 background 工作线程。

    然后将所有 RunWorkerCompleted 事件挂钩到等待它们的方法。

    如果您希望将其同步到您当前等待加入的代码,那么问题就简化为仅将单个事件方法同步到代码中的那个位置。

    更好的是,我建议在不阻塞的情况下异步执行您正在执行的操作,并在事件中执行您想要的操作。

    【讨论】:

      【解决方案5】:

      您是否考虑将您的线程调用与另一个“日志记录”线程包装起来?这样您就可以在线程运行之前和之后同步记录。

      类似这样的伪代码:

      int threadLogger(<parms>) {
          log("starting thread");
          retcode = ActualThreadBody(<parms>);
          log("exiting thread");
          return retcode;
      }
      

      如果您有关于已启动线程的更多信息,您也可以将其记录下来。 如果您有多种类型的线程要启动,您也可以将线程函数作为参数,听起来像您这样做。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-06-05
        • 2017-12-16
        相关资源
        最近更新 更多