.net 为什么我们需要在工作线程退出时检查ISOpening?

.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

从win32threadpool.cpp我们知道,在工作线程通过检查20s超时退出之前,它需要通过方法IsIoPending()检查是否有任何IO挂起,根据我的理解:

1,当工作线程要退出时,它必须完成它的工作并返回到线程池

2,基于上述1,对于要退出的线程,应该没有IO挂起

所以我的问题是,为什么我们需要在线程即将死亡时检查IO挂起?或者,我们如何模拟上述情况发生

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
}