ThreadPool.RegisterWaitForSingleObject是否阻止当前线程或线程池线程



通过阅读ThreadPool.RegisterWaitForSingleObject方法的文档,不清楚是否:

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

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

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

三者中的哪一个起作用?

以上都不是,2)最接近。确切的细节非常复杂,大部分代码都隐藏在CLR中,并且在.NET版本之间发生了变化。你可以看看CoreCLR源代码中的当前版本,我会给出10000英尺的视图。

关键是它没有阻塞,工作是由一个专用的非托管线程完成的。在源代码中称为"等待线程",它使用WaitForMultipleObjects()winapi函数来等待所有注册的等待。如果没有(剩下的),它就会睡觉。如果等待列表发生更改,则线程会被QueueUserApc()从中唤醒,以便可以使用更新的列表继续等待。

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

executeOnlyOnce参数很重要,顺便说一句,如果您传递false并使用ManualResetEvent,那么就会很有趣。MRE的Set()方法触发的线程爆炸是一个值得观察的有趣工件:)当您启用非托管调试时,您可以在调试器的Debug>Windows>Threads中看到等待线程。然而,它并没有一个有趣的名字。

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

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类文档中的这句话表明回调是在线程池线程上调用的。

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

最新更新