.net 为什么我们需要在工作线程退出时检查ISOpening?
从win32threadpool.cpp我们知道,在工作线程通过检查20s超时退出之前,它需要通过方法IsIoPending()检查是否有任何IO挂起,根据我的理解: 1,当工作线程要退出时,它必须完成它的工作并返回到线程池 2,基于上述1,对于要退出的线程,应该没有IO挂起 所以我的问题是,为什么我们需要在线程即将死亡时检查IO挂起?或者,我们如何模拟上述情况发生.net 为什么我们需要在工作线程退出时检查ISOpening?,.net,clr,windbg,coreclr,.net,Clr,Windbg,Coreclr,从win32threadpool.cpp我们知道,在工作线程通过检查20s超时退出之前,它需要通过方法IsIoPending()检查是否有任何IO挂起,根据我的理解: 1,当工作线程要退出时,它必须完成它的工作并返回到线程池 2,基于上述1,对于要退出的线程,应该没有IO挂起 所以我的问题是,为什么我们需要在线程即将死亡时检查IO挂起?或者,我们如何模拟上述情况发生 RetryWaitForWork: if (!WorkerSemaphore->Wait(AppX::IsAppXProce
RetryWaitForWork:
if (!WorkerSemaphore->Wait(AppX::IsAppXProcess() ? WorkerTimeoutAppX : WorkerTimeout))
{
if (!IsIoPending())
{
while (true)
{
RetryRetire:
DWORD result = RetiredWorkerSemaphore->Wait(AppX::IsAppXProcess() ? WorkerTimeoutAppX : WorkerTimeout, FALSE);
_ASSERTE(WAIT_OBJECT_0 == result || WAIT_TIMEOUT == result);
if (WAIT_OBJECT_0 == result)
{
foundWork = true;
counts = WorkerCounter.GetCleanCounts();
FireEtwThreadPoolWorkerThreadRetirementStop(counts.NumActive, counts.NumRetired, GetClrInstanceId());
goto Work;
}
if (!IsIoPending())
{
如果搜索
isiopening
(这是我遇到不熟悉的东西时首先要做的事情之一),您会看到,在下面的注释中,会调用它:
这几乎回答了你的问题。为什么我们需要在允许工作线程退出之前检查它是否有任何I/O挂起?因为我们不能退出一个有挂起I/O的线程
我想剩下的唯一问题是,为什么不呢?为什么我们不能退出具有挂起的I/O的线程?为了研究这一点,让我们看看isiopening
实际上做了什么。再往下搜索文件,您会发现:
//如果线程上存在挂起的io,则返回true。
BOOL ThreadpoolMgr::IsIoPending()
{
合同
{
诺思罗;
任何模式;
GC_NOTRIGGER;
}
合同结束;
#ifndef功能
智力状态;
乌龙伊斯开丁;
if(g_pufnNtQueryInformationThread)
{
状态=(int)(*g_pufnNtQueryInformationThread)(GetCurrentThread(),
拆线,
&伊希丁,
sizeof(IsOpening),
无效);
如果((状态<0)| |正在打开)
返回TRUE;
其他的
返回FALSE;
}
返回TRUE;
#否则
返回FALSE;
#endif//!FEATURE\u PAL
}
这里的注释并没有告诉我们太多,只是确认了函数的名称是否恰当,以及它是否做了我们认为它做的事情!但它的实施情况如何
首先,你会注意到,大多数有趣的东西都被一个#ifndef FEATURE\u PAL
条件测试隔开了。那么什么是功能?PAL代表PlatformAdaptionLayer,它只是一种标记代码的方法,只能在Windows上使用。如果定义了FEATURE\u PAL
,则框架是为Windows以外的操作系统编译的,因此需要排除特定于Windows的代码。这正是您在这里看到的,当定义了FEATURE\u FAL
时,这个isiopening
函数只返回FALSE
。只有当它在Windows上运行时(当未定义FEATURE\u PAL
时),才需要检查I/O是否挂起。这非常强烈地表明,上面关于无法退出具有挂起I/O的线程的评论是指Windows操作系统的规则
如果我们在Windows上运行,会发生什么?调用(通过全局函数指针间接调用)Windows API函数。第一个参数传递当前线程的句柄(GetCurrentThread()
),第二个参数请求线程信息类ThreadIsOpening
,接下来的两个参数允许函数填充ULONG
变量IsOpending
(它们传递其大小和指向它的指针)
如果您尝试阅读NtQueryInformationThread
的文档,您会发现它是一个内部函数,建议应用程序:
改为使用公共函数获取此信息
NET源代码没有遵循此建议,因为该函数(GetThreadIOPendingFlag
)是在Windows XP SP1和.NET 4(可能是编写此代码的较旧版本)需要在Windows的底层版本上运行时才引入的。因此,他们只是调用了所有受支持的Windows版本上可用的内部函数
无论如何,GetThreadIOPendingFlag
的文档几乎证实了它所做的事情,我们可能会怀疑它所做的事情:如果线程有任何I/O请求挂起,它将返回true,否则返回false。.NET Framework实现调用的内部函数将返回相同的信息
现在我想我们回到了最初的问题:为什么一个线程是否有任何挂起的I/O很重要?为什么我们要在杀死它之前检查它?嗯,在Windows中,线程发出的I/O请求与特定线程有着不可分割的联系。无法将此I/O请求或其结果数据的所有权移动到另一个线程。换句话说,用户模式不能超过最初创建它们的线程。如果线程退出,所有挂起的I/O都将被取消,并且永远不会完成。因此,我们在原始注释中得到了非常简洁的规则:如果一个线程有挂起的I/O,它就不能退出(因为那样的话,I/O将永远不会完成,也将永远丢失)
GetThreadIOPendingFlag
函数(或带有类ThreadIsOpening
的NtQueryInformationThread
)只检查指定线程的活动IRP列表,以查看它是否为空。如果没有挂起的I/O请求,那么退出线程是安全的
工作线程可能具有挂起的I/O请求的原因有很多,但最常见的情况是线程发出了。在这种情况下,在发出I/O完成信号之前,超时可能已经过去。异步I/O依赖于issuin
// Returns true if there is pending io on the thread.
BOOL ThreadpoolMgr::IsIoPending()
{
CONTRACTL
{
NOTHROW;
MODE_ANY;
GC_NOTRIGGER;
}
CONTRACTL_END;
#ifndef FEATURE_PAL
int Status;
ULONG IsIoPending;
if (g_pufnNtQueryInformationThread)
{
Status =(int) (*g_pufnNtQueryInformationThread)(GetCurrentThread(),
ThreadIsIoPending,
&IsIoPending,
sizeof(IsIoPending),
NULL);
if ((Status < 0) || IsIoPending)
return TRUE;
else
return FALSE;
}
return TRUE;
#else
return FALSE;
#endif // !FEATURE_PAL
}