Студопедия

Главная страница Случайная страница

КАТЕГОРИИ:

АвтомобилиАстрономияБиологияГеографияДом и садДругие языкиДругоеИнформатикаИсторияКультураЛитератураЛогикаМатематикаМедицинаМеталлургияМеханикаОбразованиеОхрана трудаПедагогикаПолитикаПравоПсихологияРелигияРиторикаСоциологияСпортСтроительствоТехнологияТуризмФизикаФилософияФинансыХимияЧерчениеЭкологияЭкономикаЭлектроника






Основы многопоточного программирования






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

Основные классы, предназначенные для поддержки многопоточности, сосредоточены в пространстве имён System.Threading. На платформе.NET каждый поток выполнения (thread) представлен объектом класса Thread. Для организации собственного потока необходимо создать объект этого класса. Класс Thread имеет четыре перегруженные версии конструктора:

public Thread(ThreadStart start);

public Thread(ThreadStart start, int maxStackSize);

public Thread(ParameterizedThreadStart start);

public Thread(ParameterizedThreadStart start, int maxStackSize);

В качестве первого аргумента конструктору передаётся делегат, инкапсулирующий метод, выполняемый в потоке. Доступно два типа делегатов: второй позволяет при запуске метода передать ему данные в виде объекта:

public delegate void ThreadStart();

public delegate void ParameterizedThreadStart(object obj);

Дополнительный параметр конструктора класса Thread может использоваться для указания максимального размера стека, выделяемого потоку[24].

Создание потока не подразумевает его автоматического запуска. Для запуска потока требуется вызвать метод Start() (перегруженная версия метода получает объект, передаваемый как аргумент методу потока).

var th = new Thread(DoSomeWork);

th.Start();

Рассмотрим основные свойства класса Thread:

1. Статическое свойство CurrentThread возвращает объект, представляющий текущий поток.

2. Свойство Name служит для назначения потоку имени.

3. Целочисленное свойство для чтения ManagedThreadId возвращает уникальный числовой идентификатор управляемого потока.

4. Свойство для чтения ThreadState, значением которого являются элементы одноимённого перечисления, позволяет получить текущее состояние потока.

5. Булево свойство для чтения IsAlive позволяет определить, выполняется ли поток.

6. Свойство Priority управляет приоритетом выполнения потока относительно текущего процесса. Значением этого свойства являются элементы перечисления ThreadPriority: Lowest, BelowNormal, Normal, AboveNormal, Highest.

7. Булево свойство IsBackground позволяет сделать поток фоновым. Среда исполнения.NET разделяет все потоки на фоновые и основные. Процесс не может завершиться, пока не завершены все его основные потоки. В то же время, завершение процесса автоматически прерывает все фоновые потоки[25].

8. Свойства CurrentCulture и CurrentUICulture имеют тип CultureInfo и задают текущую языковую культуру.

Следующий пример демонстрирует настройку свойств потока.

var th = new Thread(DoSomeWork)

{

Name = " Example Thread",

Priority = ThreadPriority.BelowNormal,

IsBackground = true,

CurrentCulture = new CultureInfo(" ru-RU")

};

Кроме свойств, класс Thread содержит методы для управления потоком. Метод Suspend() вызывает приостановку потока, метод Resume() возобновляет работу потока[26]. Статический метод Sleep() приостанавливает выполнение текущего потока на указанное количество миллисекунд или значение TimeSpan. Статический метод Yield() передаёт управление следующему ожидающему потоку системы. Метод Join() позволяет дождаться завершения работы того потока, у которого вызывается. Модификация данного метода блокирует выполнение текущего потока на указанное количество времени.

var th = new Thread(DoSomeWork);

th.Start(); // создали и запустили поток

th.Join(); // ждём, пока поток отработает

th.Start(); // запустили поток заново

 

// если дождались завершения за секунду, res = true

bool res = th.Join(1000);

Рис. 14 демонстрирует временную диаграмму работы потоков.

Рис. 14. Временная диаграмма работы потоков.

Для завершения работы выбранного потока используется метод Abort(). Данный метод генерирует специальное исключение ThreadAbortException. Особенность исключения состоит в том, что его невозможно подавить при помощи catch-блока. Исключение может быть отслежено тем потоком, который кто-то собирается уничтожить, а при помощи статического метода потока ResetAbort() запрос на уничтожение можно отклонить.

public class MainClass

{

public static void ThreadProc()

{

while (true)

{

try

{

Console.WriteLine(" Do some work...");

Thread.Sleep(1000);

}

catch (ThreadAbortException e)

{

// отлавливаем попытку уничтожения и отменяем её

Console.WriteLine(" Somebody tries to kill me! ");

Thread.ResetAbort();

}

}

}

 

public static void Main()

{

// запускаем поток и ждём три секунды

var th = new Thread(ThreadProc);

th.Start();

Thread.Sleep(3000);

 

// пытаемся прервать работу потока th и ждём его завершения

th.Abort();

th.Join();

 

//... но не дождёмся, так как поток сам себя " воскресил"

}

}

На рис. 15 показана диаграмма состояний потока с указанием значения свойства ThreadState.

Рис. 15. Диаграмма состояний потока.

Для выполнения в отдельном потоке повторяющегося метода можно применить класс Timer из пространства имён System.Threading. Конструктор таймера позволяет указать, через какой промежуток времени метод таймера должен выполниться первый раз, а также задать периодичность выполнения (эти величины можно затем изменить при помощи метода Change()).

using System;

using System.Threading;

 

public class MainClass

{

private static bool TickNext = true;

 

public static void Main()

{

var timer = new Timer(TickTock, null, 1000, 2000);

Console.WriteLine(" Press < Enter> to terminate...");

Console.ReadLine();

}

 

private static void TickTock(object state)

{

Console.WriteLine(TickNext? " Tick": " Tock");

TickNext =! TickNext;

}

}

Создание отдельного потока – это довольно «затратная» операция с точки зрения расхода времени и памяти. Платформа.NET поддерживает специальный механизм, называемый пул потоков. Пул потоков используют многие классы и технологии платформы.NET – асинхронные делегаты, таймеры, ASP.NET.

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

Для работы с пулом используется статический класс ThreadPool. Метод SetMaxThreads() позволяет изменить ёмкость пула, которая по умолчанию равна 1023 (в.NET Framework 4, на 32-битной ОС). Метод SetMinThreads() устанавливает количество рабочих потоков, создаваемых без задержки. По умолчанию их число равно количеству процессорных ядер. Для помещения метода в очередь пула служит метод QueueUserWorkItem(). Он принимает делегат типа WaitCallback[27] и, возможно, аргумент инкапсулируемого метода.

public static void Main()

{

ThreadPool.QueueUserWorkItem(Go);

ThreadPool.QueueUserWorkItem(Go, 123);

Console.ReadLine();

}

 

private static void Go(object data)

{

Console.WriteLine(" Hello from the thread pool! " + data);

}


Поделиться с друзьями:

mylektsii.su - Мои Лекции - 2015-2024 год. (0.01 сек.)Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав Пожаловаться на материал