在等待
EventWaitHandle
时阻塞当前线程,然后在线程池线程上委托WaitOrTimerCallback
,还是委托线程池线程等待等待处理程序,然后在同一线程上执行
WaitOrTimerCallback
,一旦等待处理程序被标记。阻止当前线程,并在等待处理程序发出信号时在当前线程上调用
WaitOrTimerCallback
。 但这将是WaitHandle.WaitOne()
的等效功能。 此外,它根本不涉及线程池。
在等待 EventWaitHandle
时阻塞当前线程,然后在线程池线程上委托 WaitOrTimerCallback
,还是
委托线程池线程等待等待处理程序,然后在同一线程上执行 WaitOrTimerCallback
,一旦等待处理程序被标记。
阻止当前线程,并在等待处理程序发出信号时在当前线程上调用 WaitOrTimerCallback
。 但这将是 WaitHandle.WaitOne()
的等效功能。 此外,它根本不涉及线程池。
以上选项都不是,2) 最接近。但具体细节相当复杂,大部分代码都隐藏在CLR中,并且它已经在.NET版本之间发生了变化。你可以看一下当前版本的CoreCLR源码,我会给出一个简单概述。
关键点是它不阻塞,工作是由专用非托管线程完成的。在源代码中被称为"wait thread",它使用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。
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();
}
}
}
QueueUserAPC
的定义/主体?如何搜索函数的主体?我刚刚在浏览器的查找窗口中使用了它,因为对QueueUserAPC
的调用没有任何接收器/对象的限定词,所以我在同一个文件中进行了搜索,但是找不到它。此外,在该文件的RegisterWaitForSingleObject
方法的源代码中,我没有看到任何对WaitForMultipleObjects/Ex
的调用。你在哪里找? - Water Cooler v2QueueUserAPC
的文档。虽然我是Win32 API的忠实粉丝和长期用户,但我认为在理解那里发生了什么之前,我仍然需要阅读很多资料。:-)非常感谢您的帮助,一如既往。我想我需要继续学习线程大约一年左右才能理解它。 - Water Cooler v2