Работа с потоками
67C# и .NET --- Многопоточность и файлы --- Работа с потоками
Потоки переднего плана и фоновые потоки
Теперь, когда известно, как создавать новые потоки выполнения программно с помощью типов из пространства имен System.Threading, давайте проясним разницу между потоками переднего плана и фоновыми потоками.
Потоки переднего плана (foreground threads) обеспечивают предохранение текущего приложения от завершения. Среда CLR не остановит приложение (что означает выгрузку текущего домена приложения) до тех пор, пока не будут завершены все потоки переднего плана.
Фоновые потоки (background threads) воспринимаются средой CLR как расширяемые пути выполнения, которые в любой момент времени могут игнорироваться (даже если они в текущее время заняты выполнением некоторой части работы). Таким образом, если все потоки переднего плана прекращаются, то все фоновые потоки автоматически уничтожаются при выгрузке домена приложения.
Важно отметить, что потоки переднего плана и фоновые потоки — это не синонимы первичных и рабочих потоков. По умолчанию каждый поток, создаваемый через метод Thread.Start(), автоматически становится потоком переднего плана. Это означает, что домен приложения не выгрузится до тех пор, пока все потоки выполнения не завершат свою часть работы. В большинстве случаев именно такое поведение и нужно.
Чтобы создать фоновый поток необходимо установить свойство IsBackground в true.
Приоритеты потоков
Как упоминалось ранее, за планирование потоков к запуску отвечает операционная система. На этот процесс планирования можно влиять, назначая потокам приоритеты. Прежде чем менять приоритет, нужно разобраться в том, как функционирует планировщик потоков. Операционная система планирует выполнение потоков на основе их приоритетов. Поток с наивысшим приоритетом начинает выполняться в ЦП первым. Поток прекращает выполнение и освобождает ЦП, если ему требуется ожидание какого-то ресурса.
Есть несколько причин для перехода потока в режим ожидания. Например, это может происходить из-за получения команды на засыпание, из-за необходимости ожидать завершение дисковых операций ввода-вывода, поступление сетевого пакета и т.п. Если поток не освобождает ЦП самостоятельно, его вытесняет планировщик потоков. Если поток имеет выделенный квант времени, он может использовать ЦП непрерывно.
Если выполняется несколько потоков с одинаковым приоритетом, каждый из которых ожидает получения доступа к ЦП, планировщик потоков применяет алгоритм кругового обслуживания, предоставляя этим потокам доступ к ЦП по очереди. В случае вытеснения поток помещается в конец очереди.
Алгоритм кругового обслуживания и кванты времени применяются только тогда, когда выполняется множество потоков с одинаковым приоритетом. Приоритет является динамическим. Если поток интенсивно использует ЦП (постоянно требует доступа к ЦП без перерывов на ожидание ресурсов), его приоритет понижается до уровня базового приоритета, который был определен с данным потоком. Если поток ожидает какой-то ресурс, поток получает "форсаж" приоритета, и его приоритет повышается. Благодаря "форсажу" вероятность того, что поток получит доступ к ЦП в следующий раз, когда завершится ожидание, значительно увеличивается.
В классе Thread базовый приоритет потока устанавливается в свойстве Priority. Допустимые значения определены в перечислении ThreadPriority. Эти значения представляют различные уровни приоритета и выглядят следующим образом: Highest, AboveNormal, Normal, BelowNormal и Lowest.
При назначении потоку более высокого приоритета следует соблюдать осторожность, поскольку это уменьшает шансы на выполнение для других потоков. Если это настоятельно необходимо, то лучше изменять приоритет только на короткое время.
Управление потоками
Поток создается за счет вызова метода Start() объекта Thread. Однако после вызова метода Start() новый поток все еще пребывает не в состоянии Running, а в состоянии Unstarted. В состояние Running поток переходит сразу после того, как планировщик потоков операционной системы выберет его для выполнения. Информация о текущем состоянии потока доступна через свойство Thread.ThreadState.
С помощью метода Thread.Sleep() поток можно перевести в состояние WaitSleepJoin и при этом указать, через какой промежуток времени поток должен возобновить работу.
Чтобы остановить поток, необходимо вызвать метод Thread.Abort(). При вызове этого метода в соответствующем потоке генерируется исключение типа ThreadAbortException. В случае если для этого исключения предусмотрен обработчик, перед завершением поток сможет выполнить необходимые операции по очистке. Чтобы продолжить выполнение потока после выдачи исключения ThreadAbortException, следует вызвать метод Thread.ResetAbort(). Состояние потока, получающего запрос на немедленное прекращение, изменяется с AbortRequested на Aborted, если поток не производит сброс.
Если необходимо дожидаться завершения работы потока, можно вызвать метод Thread.Join(). Этот метод блокирует текущий поток и переводит его в состояние WaitSleepJoin до тех пор, пока не будет завершен присоединенный к нему поток.