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

时间:2016-05-28 13:22:51

标签: c# .net multithreading clr

从阅读the documentation of the ThreadPool.RegisterWaitForSingleObject方法来看,目前尚不清楚:

  1. 它在等待EventWaitHandle时阻塞当前线程,然后在线程池线程上委托WaitOrTimerCallback,或者

  2. 它委托线程池线程在等待句柄上等待,然后在同一个线程上执行WaitOrTimerCallback一旦等待句柄发出信号。

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

  4. 它的三个中有哪一个?

3 个答案:

答案 0 :(得分:11)

以上都不是,2)是最接近的。确切的细节非常复杂,大部分代码都隐藏在CLR中,并且它已经在.NET版本中发生了变化。您可以查看当前版本in the CoreCLR source,我会给出10,000英尺的视图。

关键是它不会阻塞,工作由专用的非托管线程完成。在源代码中称为“等待线程”,它使用WaitForMultipleObjects()winapi函数等待所有已注册的等待。如果没有(左)它只是睡觉。如果等待列表发生更改,则可以通过QueueUserApc()唤醒该线程,以便它可以使用更新的列表继续等待。

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

executeOnlyOnce参数很重要btw,如果你传递 false 并且使用ManualResetEvent,则会出现欢闹。由MRE的Set()方法触发的线程爆炸是一个有趣的工件,可以观察:)你可以在调试器的Debug>中看到等待线程。 Windows>启用非托管调试时的线程。但它没有一个有趣的名字。

答案 1 :(得分:1)

以下测试代码演示了足以回答您问题的行为。

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 :(得分:1)

我做了以下测试来回答我的问题。答案是#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();
        }
    }
}

此外,来自the documentation of the ThreadPool class的这个特定句子表明在线程池线程上调用了回调。

使用已注册的等待句柄时,系统线程会监视等待句柄的状态。等待操作完成后,来自线程池的工作线程将执行相应的回调函数。