Простое описание рабочих потоков и потоков ввода-вывода в .NET

Очень сложно найти подробное, но простое описание рабочих потоков и потоков ввода-вывода в .NET.

Что мне ясно по этой теме (но может быть технически неточным):

  • Рабочие потоки - это потоки, которые должны использовать ЦП для своей работы;
  • Потоки ввода-вывода (также называемые «потоками порта завершения») должны использовать для своей работы драйверы устройств и по сути «ничего не делать», а только отслеживать завершение операций, не связанных с процессором.

Что не понятного:

  • Хотя метод ThreadPool.GetAvailableThreads возвращает количество доступных потоков обоих типов, похоже, что нет общедоступного API для планирования работы для потока ввода-вывода. Вы можете только вручную создать рабочий поток в .NET?
  • Кажется, что один поток ввода-вывода может контролировать несколько операций ввода-вывода. Это правда? Если да, то почему ThreadPool по умолчанию имеет так много доступных потоков ввода-вывода?
  • В некоторых текстах я читал этот обратный вызов, запускаемый после завершения операции ввода-вывода, выполняемой потоком ввода-вывода. Это правда? Разве это не задача рабочего потока, учитывая, что этот обратный вызов является операцией ЦП?
  • Чтобы быть более конкретным - выполняются ли потоки ввода-вывода пользователей асинхронных страниц ASP.NET? В чем именно преимущество производительности при переключении операций ввода-вывода на отдельные потоки вместо увеличения максимального количества рабочих потоков? Это потому, что один поток ввода-вывода отслеживает несколько операций? Или Windows делает более эффективное переключение контекста при использовании потоков ввода-вывода?

person Konstantin    schedule 20.01.2010    source источник


Ответы (4)


Термин «рабочий поток» в .net / CLR обычно относится к любому потоку, отличному от основного, который выполняет некоторую «работу» от имени приложения, создавшего поток. «Работа» действительно может означать что угодно, включая ожидание завершения некоторого ввода-вывода. ThreadPool хранит кеш рабочих потоков, потому что создание потоков дорого.

Термин «поток ввода-вывода» в .net / CLR относится к потокам, которые ThreadPool резервирует для отправки обратных вызовов NativeOverlapped из «перекрывающихся» вызовов win32 (также известных как «ввод-вывод порта завершения»). CLR поддерживает свой собственный порт завершения ввода-вывода и может привязать к нему любой дескриптор (через API ThreadPool.BindHandle). Пример здесь: http://blogs.msdn.com/junfeng/archive/2008/12/01/threadpool-bindhandle.aspx. Многие API-интерфейсы .net используют этот механизм внутри себя для получения обратных вызовов NativeOverlapped, хотя типичный разработчик .net никогда не будет использовать его напрямую.

На самом деле нет никакой технической разницы между «рабочим потоком» и «потоком ввода-вывода» - они оба являются обычными потоками. Но CLR ThreadPool хранит отдельные пулы для каждого просто, чтобы избежать ситуации, когда высокий спрос на рабочие потоки исчерпывает все потоки, доступные для отправки собственных обратных вызовов ввода-вывода, что может привести к тупиковой ситуации. (Представьте себе приложение, использующее все 250 рабочих потоков, каждый из которых ожидает завершения некоторого ввода-вывода).

Разработчику необходимо проявлять некоторую осторожность при обработке обратного вызова ввода-вывода, чтобы гарантировать, что поток ввода-вывода возвращается в ThreadPool, то есть код обратного вызова ввода-вывода должен выполнять минимальную работу, необходимую для обслуживания обратного вызова. а затем вернуть управление потоком пулу потоков CLR. Если требуется дополнительная работа, ее следует запланировать в рабочем потоке. В противном случае приложение рискует «захватить» пул зарезервированных потоков завершения ввода-вывода CLR для использования в качестве обычных рабочих потоков, что приведет к описанной выше ситуации взаимоблокировки.

Некоторые полезные ссылки для дальнейшего чтения: Порты завершения ввода-вывода win32: http://msdn.microsoft.com/en-us/library/aa365198(VS.85).aspx управляемый пул потоков: http://msdn.microsoft.com/en-us/library/0ka9477y.aspx пример BindHandle: http://blogs.msdn.com/junfeng/archive/2008/12/01/threadpool-bindhandle.aspx

person alexdej    schedule 30.01.2010
comment
Поэтому некоторые API-интерфейсы, которые предоставляют шаблон APM (например, WebRequest), используют метод ThreadPool.BindHandle внутри себя. Метод BeginXXX принимает делегат обратного вызова пользователя, передает работу определенному драйверу устройства и резервирует поток ввода-вывода ThreadPool для ожидания порта завершения, чтобы уведомить о завершении работы внешнего устройства. Когда уведомление получено, поток ввода-вывода просыпается и запускает код делегата обратного вызова пользователя (который должен быть быстрым, чтобы быстро вернуть поток ввода-вывода в пул). - person Konstantin; 31.01.2010
comment
Таким образом, рабочие потоки обычно выполняют работу, запланированную пользовательским кодом. А потоки ввода-вывода обычно выполняют работу, запланированную кодом фреймворка, который взаимодействует с внешними устройствами и должен ждать завершения своей работы и запускать логику обратного вызова пользователя, когда эта внешняя работа завершена. Все ли это правильное понимание? - person Konstantin; 31.01.2010
comment
Почти, но то, как ОС планирует фактический ввод-вывод, непонятно для CLR. Потоки ввода-вывода CLR не связаны ожиданием завершения; ОС скорее предупреждает CLR через порт завершения ввода-вывода, когда работа завершена. - person alexdej; 01.02.2010
comment
Я знаю, что этот вопрос старый, но, возможно, некоторые все еще могут ответить на него для меня: если я вызову несколько методов BeginXXX (например, несколько клиентских сетевых потоков BeginRead), каждый из них получит свой собственный поток ввода-вывода или все они будут обработаны в тот же поток ввода-вывода? Я спрашиваю об этом, потому что я хочу создать сервер с большим количеством клиентов, и это может вызвать проблемы, если все эти методы BeginX получат свой собственный поток, не уверен в этом. - person R1PFake; 23.09.2018

Я начну с описания того, как асинхронный ввод-вывод используется программами в NT.

Возможно, вы знакомы с функцией ReadFile Win32 API (в качестве примера), которая является оболочкой для функции NtReadFile собственного API. Эта функция позволяет делать две вещи с асинхронным вводом-выводом:

  • Вы можете создать объект события и передать его в NtReadFile. Затем об этом событии будет сообщено, когда операция чтения завершится.
  • Вы можете передать функцию вызова асинхронной процедуры (APC) в NtReadFile. По сути, это означает, что после завершения операции чтения функция будет поставлена ​​в очередь потоку, который инициировал операцию, и будет выполняться, когда поток выполнит предупреждающее ожидание.

Однако существует третий способ уведомления о завершении операции ввода-вывода. Вы можете создать объект Порт завершения ввода-вывода и связать с ним дескрипторы файлов. Каждый раз, когда операция завершается над файлом, который связан с портом завершения ввода-вывода, результаты операции (например, состояние ввода-вывода) ставятся в очередь на порт завершения ввода-вывода. Затем вы можете настроить выделенный поток для удаления результатов из очереди и выполнения соответствующих задач, таких как вызов функций обратного вызова. По сути, это и есть «рабочий поток ввода-вывода».

Обычный «рабочий поток» очень похож; вместо удаления результатов ввода-вывода из очереди он удаляет рабочие элементы из очереди. Вы можете поставить в очередь рабочие элементы (QueueUserWorkItem), чтобы рабочие потоки выполняли их. Это избавляет вас от необходимости запускать поток каждый раз, когда вы хотите выполнить задачу асинхронно.

person wj32    schedule 20.01.2010

Проще говоря, рабочий поток предназначен для выполнения короткого периода работы и удалит себя, когда завершит ее. Обратный вызов может использоваться для уведомления родительского процесса о его завершении или для передачи данных.

Поток ввода-вывода будет выполнять одну и ту же операцию или серию операций непрерывно, пока не будет остановлен родительским процессом. Это так называется, потому что обычно драйверы устройств постоянно контролируют порт устройства. Поток ввода-вывода обычно создает события всякий раз, когда он желает взаимодействовать с другими потоками.

Все процессы выполняются как потоки. Ваше приложение работает как поток. Любой поток может порождать рабочие потоки или потоки ввода-вывода (как вы их называете).

Всегда существует точный баланс между производительностью и количеством или типом используемых потоков. Слишком много обратных вызовов или событий, обрабатываемых процессом, серьезно ухудшат его производительность из-за количества прерываний основного цикла процесса при их обработке.

Примерами рабочего потока могут быть добавление данных в базу данных после взаимодействия с пользователем, выполнение длинных математических вычислений или запись данных в файл. Используя рабочий поток, вы освобождаете основное приложение, это наиболее полезно для графического интерфейса пользователя, поскольку оно не зависает во время выполнения задачи.

person ChrisBD    schedule 20.01.2010

Кто-то, у кого больше навыков, чем я, бросится сюда, чтобы помочь.

Рабочие потоки имеют много состояний, они планируются процессором и т. Д., И вы контролируете все, что они делают.

Порты завершения ввода-вывода предоставляются операционной системой для очень специфических задач, связанных с небольшим общим состоянием, и поэтому их можно использовать быстрее. Хорошим примером в .Net является фреймворк WCF. Каждый «вызов» службы WCF фактически выполняется портом завершения ввода-вывода, потому что они запускаются быстрее всего, и ОС заботится о них за вас.

person Spence    schedule 20.01.2010