Если вы выполните поиск IsIoPending
(что является одним из первых действий, которые я делаю, когда сталкиваюсь с чем-то, что мне незнакомо), вы увидите, что чуть ниже он вызывается со следующим комментарием перед ним:
// We can't exit a thread that has pending I/O - we'll "retire" it instead.
Это почти отвечает на ваш вопрос. Почему нам нужно проверять, есть ли у рабочего потока какие-либо ожидающие операции ввода-вывода, прежде чем мы позволим ему выйти? Ну, потому что мы не можем выйти из потока, который ожидает ввода-вывода.
Единственный оставшийся вопрос, я полагаю, почему бы и нет? Почему мы не можем выйти из потока, ожидающего ввода-вывода? Чтобы разобраться в этом, давайте посмотрим, что IsIoPending
на самом деле делает. Поищите немного дальше по файлу, и вы найдете его реализация:
// 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
не определен), он пытается проверить, ожидает ли ввод-вывод. Это довольно убедительно свидетельствует о том, что приведенный выше комментарий о невозможности выйти из потока, имеющего ожидающий ввод-вывод, относится к правилу операционной системы Windows.
Что произойдет, если мы работаем в Windows? Выполняется вызов (косвенно, через указатель глобальной функции) к функции Windows API _ 12_. Первый параметр передает дескриптор текущего потока (GetCurrentThread()
), второй параметр запрашивает класс информации о потоке ThreadIsIoPending
, а следующие два параметра позволяют функции заполнять ULONG
переменную IsIoPending
(они передают ее размер и указатель на нее) .
Если вы попытаетесь прочитать документацию для NtQueryInformationThread
, вы увидите, что это внутренняя функция и что приложениям рекомендуется:
Используйте общедоступную функцию _18 _ вместо этого, чтобы получить эту информацию.
Исходный код .NET не следует этому совету, потому что эта функция (GetThreadIOPendingFlag
) не была представлена до Windows XP SP1, а .NET 4 (и, предположительно, более старые версии, для которых был написан этот код) необходимо было запускать в более ранних версиях Windows. . Поэтому они просто вызвали внутреннюю функцию, которая была доступна во всех поддерживаемых версиях Windows.
В любом случае, документация для GetThreadIOPendingFlag
в значительной степени подтверждает, что он делает то, что мы подозреваем, что он делает: он возвращает истину, если поток имеет какие-либо ожидающие запросы ввода-вывода, или ложь в противном случае. Внутренняя функция, вызываемая реализацией .NET Framework, вернет ту же информацию.
И теперь, я думаю, мы вернулись к исходному вопросу: почему имеет значение, есть ли у потока ожидающие операции ввода-вывода? Почему нам нужно проверять это, прежде чем убивать? Что ж, в Windows запрос ввода-вывода, выданный потоком, неразрывно привязан к этому конкретному потоку. Невозможно передать владение этим запросом ввода-вывода или его результирующими данными другому потоку. Иными словами, IRP пользовательского режима не могут пережить поток, в котором они изначально были созданы. Если поток завершен, все ожидающие операции ввода-вывода будут бесцеремонно отменены и никогда не будут завершены. Таким образом, мы получаем правило, столь лаконично сформулированное в исходном комментарии: если поток имеет ожидающий ввод-вывод, он не может быть завершен (потому что тогда этот ввод-вывод никогда не будет выполнен и будет потерян навсегда).
Функция GetThreadIOPendingFlag
(или NtQueryInformationThread
с классом ThreadIsIoPending
) просто проверяет активный список IRP для указанного потока, чтобы узнать, пуст он или нет. Если нет ожидающих запросов ввода-вывода, можно безопасно выйти из потока.
Существует множество причин, по которым рабочий поток может иметь ожидающие запросы ввода-вывода, но наиболее распространенная ситуация возникает, если перекрытый (асинхронный) запрос ввода-вывода был выдан потоком. В этом случае тайм-аут может истечь до получения сигнала о завершении ввода-вывода. Асинхронный ввод-вывод, зависящий от выдающего потока, является фундаментальным ограничением архитектуры Win32, и реализация пула потоков в .NET Framework учитывает это ограничение и учитывает его.
Возможно, эта проверка обычно возвращает false, но на всякий случай рекомендуется проверить ее явно. Это стандартные методы защитного программирования - очень важная практика для инфраструктуры, которая распространяется по всему миру, работает в широком спектре различных условий и должна быть максимально надежной.
person
Cody Gray
schedule
19.06.2017