Томский политехнический университет
Опубликован: 23.01.2013 | Доступ: свободный | Студентов: 1157 / 192 | Длительность: 12:09:00
Лекция 3:

Многопоточность в .NET Framework

Аннотация: В рамках данной лекции будут рассмотрены следующие вопросы: определение многопоточности; основы многопоточной обработки; класс Thread; создание вторичных потоков; назначение приоритета потока; управление потоками; пул потоков CLR.

Определение многопоточности

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

Многопоточность (multithreаding) - это специализированная форма многозадачности (multitаsking). В основном, выделяют два типа многозадачности: основанную на процессах (prоcess-bаsed) и основанную на потоках (threаd-bаsed).

Процесс (prоcess) - это по сути запущенная программа. Следовательно, основанная на процессах многозадачность - средство, позволяющее компьютеру выполнять несколько операций (программ) одновременно. Например, основанная на процессах многозадачность предоставляет одновременно редактировать текст в текстовом редакторе и работать с другой запущенной программой.

Основы многопоточной обработки

Всего различают две разновидности многозадачности: на основе процессов и на основе потоков. Отличия многозадачности на основе процессов и потоков сводится к следующему: многозадачность на основе процессов организуется для параллельного выполнения программ, а многозадачность на основе потоков - для параллельного выполнения отдельных частей одной программы. В .NET Framework поддерживаются обе разновидности многозадачности: на основе процессов и на основе потоков. Поэтому можно создавать как процессы, так и потоки, а также управлять и теми и другими.

Классы, поддерживающие многопоточное программирование, определены в пространстве имен System.Threading. Поэтому любая многопоточная программа включает в себя следующую строку кода (C#):

using System.Threading;

Пространство имен System.Threading содержит различные типы, позволяющие создавать многопоточные приложения. Одним из основных классов в данном пространстве имен является класс Thread.

Класс Thread

Класс Thread является самым элементарным из всех типов пространства имен System.Threading. Основные свойства и методы класса Thread приведены в таблице ниже.

Таблица 4.1. Основные свойства и методы класса Thread
Имя Описание
CurrentContext Это свойство только для чтения возвращает контекст, в котором в данный момент выполняется поток
CurrentThread Это свойство только для чтения возвращает ссылку на текущий выполняемый поток
GetDomain(), GetDomainID() Этот метод возвращает ссылку на текущий AppDomain или идентификатор этого домена, в котором выполняется текущий поток
Sleep() Этот метод приостанавливает текущий поток на заданное время
IsAlive Возвращает булевское значение, указывающее на то, запущен ли поток (и еще не прерван и не отменен)
IsBackground Получает или устанавливает значение, указывающее, является ли данный поток "фоновым" (подробнее объясняется далее)
Name Позволяет вам установить дружественное текстовое имя потока
Priority Получает или устанавливает приоритет потока, который может принимать значение из перечисления ThreadPriority
ThreadState Получает состояние данного потока, которому может быть присвоено значение из перечисления ThreadState
Abort() Инструктирует CLR прервать поток, как только это будет возможно
Interrupt() Прерывает (т.е. приостанавливает) текущий поток на заданный период ожидания
Join() Блокирует вызывающий поток до тех пор, пока указанный поток (тот, для которого вызван Join()) не завершится
Resume() Возобновляет ранее приостановленный поток
Start() Инструктирует CLR запустить поток как можно скорее
Suspend() Приостанавливает поток. Если поток уже приостановлен, вызов Suspend() не дает эффекта

Создание вторичных потоков

При создании многопоточного приложения, необходимо, руководствоваться следующими шагами:

  1. Создается метод, который будет точкой входа для нового потока.
  2. Создается новый делегат ParametrizedThreadStart (или ThreadStart), передав конструктору метод, который определен на предыдущем шаге.
  3. Создается объект Thread, передав в качестве аргумента конструктора ParametrizedThreadStart/ThreadStart.
  4. Устанавливается начальные характеристики потока (имя, приоритет и т.п.).
  5. Вызывается метод Thread.Start(). Это действие запустит поток на методе, который указан делегатом, созданным на втором шаге, как только это будет возможно.

Всего различают два типа потоков:

  • Потоки переднего плана (foreground threads) или приоритетный. По умолчанию каждый поток, создаваемый через метод Thread.Start(), автоматически становится потоком переднего плана. Данный тип потоков обеспечивают предохранение текущего приложения от завершения. Среда CLR не остановит приложение до тех пор, пока не будут завершены все приоритетные потоки.
  • Фоновые потоки (background threads). Данный вид потоков называется также потоками-демонами, воспринимаются средой CLR как расширяемые пути выполнения, которые в любой момент времени могут игнорироваться. Таким образом, если все потоки переднего плана прекращаются, то все фоновые потоки автоматически уничтожаются при выгрузке домена приложения. Для создания фоновых потоков необходимо присвоить свойству IsBackground значение true.

Назначение приоритета потока

Для того что бы присвоить потоку приоритет, нужно использовать свойство Priority в классе Thread, которое определяет, сколько времени на исполнение будет выделено потоку относительно других потоков того же процесса. Существует 5 градаций приоритета потока:

enum ThreadPriority { Lowest, BelowNormal, Normal, AboveNormal, Highest }

Значение приоритета становится существенным, когда одновременно исполняются несколько потоков. Установка приоритета потока на максимум еще не означает работу в реальном времени (real-time), так как существуют еще приоритет процесса приложения. Для того, чтобы поток работал в режиме реального времени (real-time), используется класс Process из пространства имен System.Diagnostics для поднятия приоритета:

Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;

От ProcessPriorityClass.High один шаг до наивысшего приоритета процесса - Realtime. При использовании процесса в режиме приоритета Realtime, то процесс никогда не вытиснится, и если программа случайно попадет в бесконечный цикл, операционная система может быть полностью заблокирована. Спасти вас в этом случае сможет только кнопка выключения питания или перезагрузки. По этой причине ProcessPriorityClass.High считается максимальным приоритетом процесса, рекомендованным к использованию.

Управление потоками

Поток создается за счет вызова метода Start() объекта Thread. Однако после вызова метода Start() новый поток все еще пребывает в состоянии Unstarted. В состояние Running поток переходит сразу после того, как планировщик потоков операционной системы выберет его для выполнения. Информация о текущем состоянии потока доступна через свойство Thread.ThreadState.

С помощью метода Thread.Sleep() поток можно перевести в состояние WaitSleepJoin и при этом указать, через какой промежуток времени поток должен возобновить работу:

thread.Sleep(1000);

Для прерывания работы потока, используется метод Thread.Abort():

thread.Abort()

При вызове этого метода в соответствующем потоке генерируется исключение типа ThreadAbortException в том потоке, для которого он был вызван. Это исключение приводит к прерыванию потока и может быть перехвачено и в коде-программы, но в этом случае оно автоматически генерируется еще раз, чтобы остановить поток. Метод Abort() не всегда способен остановить поток немедленно, поэтому если поток требуется остановить перед тем, как продолжить выполнение программы, то после метода Abort() следует сразу же вызвать метод Join().Кроме того, в самых редких случаях методу Abort() вообще не удается остановить поток. Это происходит, например, в том случае, если кодовый блок finally входит в бесконечный цикл.

Запрос на преждевременное прерывание может быть переопределен в самом потоке. Для этого необходимо сначала перехватить в потоке исключение ThreadAbortException, а затем вызвать метод ResetAbort(). Благодаря этому исключается повторное генерирование исключения по завершении обработчика исключения, прерывающего данный поток. Ниже приведена форма объявления метода ResetAbort():

Thread.ResetAbort();

Вызов метода ResetAbort() может завершиться неудачно, если в потоке отсутствует надлежащий режим надежной отмены преждевременного прерывания потока.

Владимир Каширин
Владимир Каширин

Вопрос по Курсу: "Параллельное программирование с использованием MS VisualStudia 2010".

При компиляции Самостоятельного задания (одновременная отрисовка прямоугольников, эллипсов и выдача в текст-бокс случайного числа) среда предупреждает: suspend - устаревшая команда; примените monitor, mutex и т.п.

Создаётся впечатление, что Задание создано в более поздней среде, чем VS 2010.