ThreadPool.RegisterWaitForSingleObject会阻塞当前线程还是线程池线程?

9
阅读 ThreadPool.RegisterWaitForSingleObject 方法的文档,不清楚它是否:
  1. 在等待 EventWaitHandle 时阻塞当前线程,然后在线程池线程上委托 WaitOrTimerCallback,还是

  2. 委托线程池线程等待等待处理程序,然后在同一线程上执行 WaitOrTimerCallback,一旦等待处理程序被标记。

  3. 阻止当前线程,并在等待处理程序发出信号时在当前线程上调用 WaitOrTimerCallback。 但这将是 WaitHandle.WaitOne() 的等效功能。 此外,它根本不涉及线程池。

它们三个中哪一个?
3个回答

19

以上选项都不是,2) 最接近。但具体细节相当复杂,大部分代码都隐藏在CLR中,并且它已经在.NET版本之间发生了变化。你可以看一下当前版本的CoreCLR源码,我会给出一个简单概述。

关键点是它不阻塞,工作是由专用非托管线程完成的。在源代码中被称为"wait thread",它使用WaitForMultipleObjects() winapi函数等待所有注册的等待。如果没有(剩下),它就会睡眠。如果等待列表发生变化,它会通过QueueUserApc()唤醒线程,以便使用更新后的列表恢复等待。

一旦其中一个等待对象被发送信号,它将使用ThreadPool.QueueUserWorkItem()在线程池线程上调用回调委托目标。如果executeOnlyOnce参数为true,则从列表中删除等待句柄。然后它会迅速恢复使用WFMO等待。该线程永远不会结束。

顺便提一下,executeOnlyOnce参数很重要,如果你传递false并使用ManualResetEvent,那么有趣的事情会发生。MRE的Set()方法触发的线程爆炸是一个有趣的现象 :) 当你启用非托管调试时,你可以在调试器的Debug > Windows > Threads中看到等待线程。不过它没有一个有趣的名字。


非常感谢。我已经有一段时间没有接触C++了,所以我对CLR源代码的理解有限,但我正在努力。我在哪里可以找到QueueUserAPC的定义/主体?如何搜索函数的主体?我刚刚在浏览器的查找窗口中使用了它,因为对QueueUserAPC的调用没有任何接收器/对象的限定词,所以我在同一个文件中进行了搜索,但是找不到它。此外,在该文件的RegisterWaitForSingleObject方法的源代码中,我没有看到任何对WaitForMultipleObjects/Ex的调用。你在哪里找? - Water Cooler v2
最后,APC是什么?我推断它是一种Win32隐喻,用于表示需要完成的工作项/工作? - Water Cooler v2
这是一个操作系统功能,所以它使用 WFMO。源代码仅对微软员工可见。只需在 MSDN 中查找即可了解其功能。APC 是异步过程调用,.NET 的 BeginInvoke() 大致类似。非常粗略地说。可执行 APC 的可警报线程的概念在 .NET 中不可用,因为正确实现太困难了。 - Hans Passant
谢谢。我刚刚查看了QueueUserAPC的文档。虽然我是Win32 API的忠实粉丝和长期用户,但我认为在理解那里发生了什么之前,我仍然需要阅读很多资料。:-)非常感谢您的帮助,一如既往。我想我需要继续学习线程大约一年左右才能理解它。 - Water Cooler v2
1
什么是“WFMO”?请为我们中没有听说过的不常见缩略语拼写出来。谢谢您为“APC”这个缩略语这么做。 - pwrgreg007
我认为他们的意思是“等待多个对象(WFMO)”。从“is”错写成“it”并没有起到任何帮助。 - Antoine

2
以下测试代码展示了足够详细的行为以回答您的问题。
static bool bQuit = false;
static string LastEntry;

static void Main(string[] args)
{
    EventWaitHandle ewh = new EventWaitHandle(false, EventResetMode.AutoReset, "TestEvent");
    ThreadPool.QueueUserWorkItem(new WaitCallback(Thread1));
    Console.WriteLine("TestEvent created.");

    while (!bQuit)
    {
        Console.WriteLine("Press 1 to signal TestEvent.\nPress 2 to quit.");
        switch (LastEntry = Console.ReadLine())
        {
            case "1":
                ewh.Set();
                break;
            case "2":
                bQuit = true;
                break;
        }
    }
    ewh.Dispose();
    Console.WriteLine("Press Enter to finish exiting.");
    Console.ReadLine();
}

static void Thread1(object data)
{
    WaitHandle wh = EventWaitHandle.OpenExisting("TestEvent");
    RegisteredWaitHandle rwh = ThreadPool.RegisterWaitForSingleObject(
        wh, new WaitOrTimerCallback(Thread2), null, Timeout.Infinite, false);
    Console.WriteLine("Thread {0} registered another thread to run when TestEvent is signaled.",
        Thread.CurrentThread.ManagedThreadId);
    while(!bQuit)
    {
        Console.WriteLine("Thread {0} is running.", Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(2000);
    }
    rwh.Unregister(wh);
    Console.WriteLine("Thread {0} is exiting", Thread.CurrentThread.ManagedThreadId);
}

static void Thread2(object data, bool t)
{
    Console.WriteLine("Thread {0} started", Thread.CurrentThread.ManagedThreadId);
    while(!bQuit && (LastEntry != Thread.CurrentThread.ManagedThreadId.ToString()))
    {
        Console.WriteLine("Thread {0} is running. Enter {0} to end it",
            Thread.CurrentThread.ManagedThreadId);
        Thread.Sleep(2000);
    }
    Console.WriteLine("Thread {0} is exiting", Thread.CurrentThread.ManagedThreadId);
}

输出结果为:
TestEvent created.
Thread 6 registered another thread to run when TestEvent is signaled.
Thread 6 is running.
Press 1 to signal TestEvent.
Press 2 to quit.
Thread 6 is running.
Thread 6 is running.
1
Press 1 to signal TestEvent.
Press 2 to quit.
Thread 13 started
Thread 13 is running. Enter 13 to end it
Thread 6 is running.
Thread 13 is running. Enter 13 to end it
Thread 6 is running.
Thread 13 is running. Enter 13 to end it
1
Press 1 to signal TestEvent.
Press 2 to quit.
Thread 14 started
Thread 14 is running. Enter 14 to end it
Thread 6 is running.
Thread 13 is running. Enter 13 to end it
Thread 14 is running. Enter 14 to end it
Thread 6 is running.
Thread 13 is running. Enter 13 to end it
Thread 14 is running. Enter 14 to end it
13
Press 1 to signal TestEvent.
Press 2 to quit.
Thread 6 is running.
Thread 13 is exiting
Thread 14 is running. Enter 14 to end it
Thread 6 is running.
Thread 14 is running. Enter 14 to end it
2
Press Enter to finish exiting.
Thread 6 is exiting
Thread 14 is exiting

因此,我认为你的问题的答案是第二个选项。

2

我进行了以下测试来回答我的问题。答案是#2。

using System;
using System.Threading;

namespace ThreadPoolRegisterWaitForSingleObject
{
    class Program
    {
        static void Main(string[] args)
        {
            var allTasksWaitHandle = new AutoResetEvent(false);

            Action action = () =>
            {
                Console.WriteLine($"Long task running on {(Thread.CurrentThread.IsThreadPoolThread ? "thread pool" : "foreground")} thread Id: {Thread.CurrentThread.ManagedThreadId}");

                for (int i = 0; i < 1000000; i++)
                    for (int j = 0; j < 100000000; j++) ;
            };

            //var result = action.BeginInvoke((state) =>
            //{
            //    Console.WriteLine("Async call back says long thing done.");
            //}, null);

            var result = action.BeginInvoke(null, null);

            Console.WriteLine("Main thread not blocked.");

            var registerWaitHandle = ThreadPool.RegisterWaitForSingleObject(result.AsyncWaitHandle, (s, b) =>
            {
                Console.WriteLine("Main long task finished.");
                Console.WriteLine($"WaitOrTimerCallback running on {(Thread.CurrentThread.IsThreadPoolThread ? "thread pool" : "foreground")} thread Id: {Thread.CurrentThread.ManagedThreadId}");

                allTasksWaitHandle.Set();
            }, null, 5000, true);

            allTasksWaitHandle.WaitOne();

            Console.WriteLine("All threads done.");
            Console.WriteLine("Press any key to exit...");
            Console.ReadKey();
        }
    }
}

此外,来自ThreadPool类的文档的这句话表明回调函数在线程池线程上被调用。
当您使用注册等待句柄时,系统线程监视等待句柄的状态。当等待操作完成时,线程池中的工作者线程将执行相应的回调函数。

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