等待ANY线程完成,而不是ALL

本文关键字:ALL ANY 线程 等待 | 更新日期: 2023-09-27 18:28:52

我正在启动多个线程,想知道的任何何时结束。我知道以下代码:

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

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

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

等待ANY线程完成,而不是ALL

使用任务而不是线程。它具有WaitAny方法。

Task.WaitAny

正如你在这里看到的,

  • 更高效、更可扩展地使用系统资源
  • 与线程或工作项相比,可以进行更多的编程控制

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

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

List<Thread> threads = new List<Thread>();
while (!threads.Any(x=> x.Join(100)))
{
}

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

我的回答是基于您的澄清,即您所拥有的只是Thread.Current免责声明:IMO,你想做的是黑客攻击,所以我的想法也是黑客攻击

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

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

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

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

[EDITED]根据目标线程的执行环境,此破解可能不起作用,因为无法保证托管和非托管线程之间的一对一映射:

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

然而,这可能仅适用于自定义CLR主机。此外,似乎可以使用Thread.BeginThreadAffinityThread.EndThreadAffinity来控制托管线程亲和性。

您可以使用后台工作线程。

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

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

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

您会考虑用另一个"日志记录"线程包装线程调用吗?这样你就可以在&在线程运行之后。

类似这样的伪代码:

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

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