等待线程完成

3
我有一个C#程序,调用了多个函数,每个函数都会调用另一个DLL中的方法。这个方法创建一个新线程来执行工作,然后立即返回。
问题是我需要我的主程序等待所有未完成的线程完成。我该如何实现?
我尝试搜索答案,得到的所有答案都说要使用TPL(但我不能使用,因为我正在使用.NET 3.5),或者获取对线程的引用并使用Thread.Join。但是由于这些线程是在另一个没有源代码访问权限的DLL文件中创建的,因此这个选项也不可行。
我该如何等待所有线程完成?

即使使用 Process.GetCurrentProcess().Threads 也无法帮助,因为它获取的是非托管线程,而它们与托管线程之间的对应关系很难确定。我不认为有什么方法可以解决这个问题。 - Ian Mercer
@Icemanind,你能获取未托管线程的ID吗? - Yacoub Massad
你的进程在所有线程终止之前不会退出。 - huysentruitw
@WouterHuysentruit 在所有前台线程终止之前,您的进程不会退出。 - Servy
"操作系统的ThreadId与托管线程没有固定的关系。" - James Thorpe
显示剩余13条评论
3个回答

0
也许您可以安装自定义的CLR分析器。这将使您访问所有托管线程。您应该能够拦截几乎所有东西。您应该能够拦截线程启动和关闭。
CLR分析器API被诸如SQL Server和ASP.NET之类的宿主使用,以执行极具侵入性的监视和行为更改,以托管.NET代码。
作为最后的手段,您甚至可以使用分析器API来运行时重写该库的IL...
这是一种可靠的方法,但可能不容易或快速完成。

0

免责声明:我发布这个答案只是因为它满足了提问者的要求,而且我并没有从Servy那里抄袭: 你无能为力。但是我建议不要在生产环境中使用这个。个人建议是放弃使用那个库,自己实现。

话虽如此,作为一个测试,我创建了一个包含一个方法DoWork的类库,该方法会启动一个后台线程,并运行10秒钟:

public class ThirdParty
{
    public static void DoWork()
    {
        new Thread(() => Thread.Sleep(10000)) { IsBackground = true }.Start();
    }
}

现在的想法是在调用ThirdParty.DoWork之前和之后比较运行的线程。在调用之后,将收集一个或多个线程ID,并使用OpenThread将其转换为线程句柄。最后,您可以调用WaitForMultipleObjects等待第三方后台线程完成其工作。
class Program
{
    [DllImport("kernel32.dll")]
    static extern uint WaitForMultipleObjects(int nCount, IntPtr[] lpHandles, bool bWaitAll, uint dwMilliseconds);

    [DllImport("kernel32.dll")]
    static extern IntPtr OpenThread(uint dwDesiredAccess, bool bInheritHandle, int dwThreadId);

    [DllImport("kernel32.dll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool CloseHandle(IntPtr hObject);

    const uint SYNCHRONIZE = 0x00100000;

    static IntPtr GetThreadHandleById(int threadId)
    {
        return OpenThread(SYNCHRONIZE, false, threadId);
    }

    static void Main(string[] args)
    {
        var threads = new List<int>();
        foreach (ProcessThread thread in Process.GetCurrentProcess().Threads)
            threads.Add(thread.Id);

        ThirdParty.DoWork();

        var threadHandlesToWaitFor = new List<IntPtr>();
        foreach (ProcessThread thread in Process.GetCurrentProcess().Threads)
            if (!threads.Contains(thread.Id))
                threadHandlesToWaitFor.Add(GetThreadHandleById(thread.Id));

        Console.WriteLine("Waiting for {0} thread(s) to finish...", threadHandlesToWaitFor.Count);
        WaitForMultipleObjects(threadHandlesToWaitFor.Count, threadHandlesToWaitFor.ToArray(), true, 0xffffffff);

        foreach (var handle in threadHandlesToWaitFor)
            CloseHandle(handle);
    }
}

实质上,我们正在等待一个似乎在衍生的托管线程上运行的非托管线程。这可能会在未来发生故障,因为.NET不能保证每个托管线程都在自己的非托管线程上运行。

1
哦,这太恶心了,以至于它变得有趣。我猜如果你不需要绝对可靠性,这是一个有效的最后手段解决方案。你永远无法控制运行在进程中的所有线程。其他线程可能会产生,而你却无法理解。 - usr
我也认为,考虑到它是一个控制台应用程序,而且没有开始使用WCF、OWIN或其他花哨的东西,这将是可靠的。 - huysentruitw
@usr添加了一份免责声明,以警告这里的疯狂人们 :) - huysentruitw

-1

假设你调用的异步方法没有提供任何完成指示(例如Task、回调、事件等),那么你就无需进行任何操作。你可以通过使用其中一种方法重构一个方法,但如果核心方法没有提供完成指示,则你无能为力。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接