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

时间:2017-06-18 13:16:35

标签: .net clr windbg coreclr

从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())
    {

https://github.com/dotnet/coreclr/blob/master/src/vm/win32threadpool.cpp

1 个答案:

答案 0 :(得分:4)

如果你搜索IsIoPending(这是我在遇到一些我不熟悉的事情时所做的第一件事),你会看到更进一步,它被调用以下在它之前发表评论:

  

// We can't exit a thread that has pending I/O - we'll "retire" it instead.

这几乎可以回答你的问题。为什么我们需要在允许它退出之前检查工作线程是否有任何待处理的I / O?好吧,因为我们无法退出具有待处理I / O的线程。

唯一剩下的问题是,我想,为什么不?为什么我们不能退出具有挂起I / O的线程?要对此进行调查,让我们看一下IsIoPending实际做什么。在文件中进一步搜索,然后找到its implementation

// 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
}

评论并没有在这里告诉我们很多,除了确认该功能已被充分命名并且它完成了我们认为的功能!但它的实施呢?

嗯,首先要注意的是,大多数有趣的东西都是通过#ifndef FEATURE_PAL条件测试来封锁的。那么FEATURE_PAL是什么? PAL代表 P latform A daptation L ayer,它只是一种标记仅适用于Windows的代码的方式。如果定义了FEATURE_PAL,那么正在为Windows 其他编译框架,因此需要排除特定于Windows的代码。这正是您在此处看到的内容 - 定义FEATURE_FAL时,此IsIoPending函数只返回FALSE。只有当它在Windows上运行时(当FEATURE_PAL 未定义时),它才会检查I / O是否挂起。这强烈暗示上面关于无法退出具有挂起I / O的线程的注释是指Windows操作系统的规则。

如果我们在Windows上运行会怎样?对Windows API函数NtQueryInformationThread进行调用(间接地,通过全局函数指针)。第一个参数传递当前线程的句柄(GetCurrentThread()),第二个参数传递线程信息类ThreadIsIoPending,接下来的两个参数允许函数填写ULONG变量IsIoPending(它们传递它的大小和指向它的指针)。

如果您尝试阅读NtQueryInformationThread的文档,您会发现它是一个内部函数,建议应用程序:

  

使用公共功能GetThreadIOPendingFlag代替获取此信息。

.NET源代码不遵循此建议,因为在Windows XP SP1和.NET 4(以及可能是旧版本,编写此代码)所需的函数(GetThreadIOPendingFlag)之后才会引入在Windows的低级版本上运行。因此,他们只调用了所有受支持的Windows版本上可用的内部函数。

无论如何,GetThreadIOPendingFlag的文档几乎证实了我们会怀疑它的作用:如果一个线程有任何待处理的I / O请求,它返回true,否则返回false。 .NET Framework实现调用的内部函数将返回相同的信息。

现在我想我们回到原来的问题:为什么一个线程是否有任何待处理的I / O?为什么我们要在杀死之前检查一下?好吧,在Windows中,线程发出的I / O请求与该特定线程密不可分。无法将此I / O请求的所有权或其结果数据移至另一个线程。换句话说,用户模式IRPs不能超过最初创建它们的线程。如果退出该线程,则将毫不客气地取消所有挂起的I / O,并且永远不会完成。因此,我们在原始注释中得到了如此简洁的规则:如果一个线程有待处理的I / O,则无法退出(因为那时I / O将永远不会被完成并且将永远丢失)。

GetThreadIOPendingFlag函数(或类NtQueryInformationThread的{​​{1}})只检查指定线程的活动IRP列表,以查看它是否为空。如果没有待处理的I / O请求,则退出该线程是安全的。

工作线程可能有待处理的I / O请求的原因有很多,但最常见的情况是线程发出overlapped (asynchronous) I/O request。在这种情况下,在发出I / O完成信号之前可能会超时。依赖于发布线程的异步I / O是Win32体系结构的一个基本限制,并且.NET Framework的线程池实现意识到了这种限制并为此负责。

这个检查通常可能会返回false,但最好明确检查它是为了安全起见。这些是标准的防御性编程实践 - 对于全球发布的框架而言,这是一个非常重要的实践,它运行在各种不同的条件下,并且需要尽可能健壮。