Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Лекции OOP c#.doc
Скачиваний:
44
Добавлен:
22.09.2019
Размер:
3.38 Mб
Скачать

2.10. МНогопоточное программирование

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

public Thread(ThreadStart start);

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

public delegate void ThreadStart();

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

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

using System;

using System.Threading; //Необходимо для работы с потоками

class MainClass {

// Эта функция будет выполняться в отдельном потоке

public static void DoSomeWork() {

while (true) {

Console.WriteLine("The second thread");

// Создаем видимость работы

for (int i = 0; i < 1000000; i++){ i++; }

}

}

public static void Main() {

// Создали объект потока

Thread th = new Thread(new ThreadStart(DoSomeWork));

// Запустили поток

th.Start();

// Создаем видимость работы

while (true) {

Console.WriteLine("The first thread");

for (int i = 0; i < 1000000; i++){ i++; }

}

}

}

Рассмотрим работу с членами класса Thread подробнее. Любой поток характеризуется приоритетом выполнения, который влияет на количество квантов процессорного времени, выделяемых потоку. Для работы с приоритетами класс Thread содержит свойство Priority, доступное для чтения и записи. Значением свойства являются элементы перечисления ThreadPriority: Lowest, BelowNormal, Normal, AboveNormal, Highest:

// Создали объект потока

Thread th = new Thread(new ThreadStart(DoSomeWork));

// Назначим потоку низкий приоритет

th.Priority = ThreadPriority.Lowest;

// Запустим поток

th.Start();

Среда исполнения платформы .NET разделяет все потоки на фоновые и основные. Главное приложение не может завершиться, пока не завершены все его основные потоки. Если работа приложения завершается, а некоторые фоновые потоки еще работают, то их работа автоматически прекращается. Таким образом, к основным следует относить такие потоки, которые выполняют критические для приложения действия. Для установки типа потока следует использовать свойство IsBackground булевого типа. Следующий пример показывает применение свойства:

using System;

using System.Threading;

class MainClass {

public static void DoSomeWork() {

while (true) {

Console.WriteLine("The second thread");

// Этот метод приостанавливает поток на 400 мсек

Thread.Sleep(400);

}

}

public static void Main() {

Thread th = new Thread(new ThreadStart(DoSomeWork));

th.IsBackground = true; // Поток будет фоновым

th.Start();

// "Усыпили" основной поток на 2 секунды

Thread.Sleep(2000);

Console.WriteLine("Quit from main thread");

}

}

По умолчанию любой поток создается как основной, поэтому, если закомментировать строку th.IsBackground = true и запустить приложение, то самостоятельно закончить работу оно не сможет.

Класс Thread предоставляет встроенные механизмы управления потоками. Метод Suspend() вызывает приостановку потока, метод Resume() возобновляет работу потока. Статический метод Sleep() приостанавливает выполнение того потока, в котором вызывается, на указанное количество миллисекунд:

// Пусть объект th связан с некоторым потоком

th.Suspend(); // Приостановили поток

th.Resume(); // Запустили снова

Thread.Sleep(2000); // "Усыпили" поток на 2 секунды

Для приостановки работы произвольного потока на заданное время может использоваться такой код (штатных методов для этого не существует):

th.Suspend();

Thread.Sleep(2000);

th.Resume();

Если вызвать метод Sleep() с параметром -1, то поток будет остановлен навсегда.

Метод Join() позволяет дождаться завершения работы того потока, у которого вызывается. Модификация данного метода блокирует выполнение текущего потока на указанное количество миллисекунд:

Thread th = new Thread(new ThreadStart(DoSomeWork));

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

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

. . .

Thread th = new Thread(new ThreadStart(DoSomeWork));

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

// Будем ждать 1 секунду. Если за это время поток th

// завершиться, то значение res будет true

bool res = th.Join(1000);

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

using System;

using System.Threading;

class MainClass {

public static void ThreadProc() {

while(true)

try {

Console.WriteLine("Some work...");

Thread.Sleep(1000);

} catch (ThreadAbortException e) {

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

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

Thread.ResetAbort();

}

}

public static void Main() {

// Создаем и запускаем поток

Thread th = new Thread(new ThreadStart(ThreadProc));

th.Start();

// Подождем 10 секунд

Thread.Sleep(10000);

// Пытаемся прервать работу потока

th.Abort();

// Дождемся завершения потока. Вернее, никогда мы его

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

th.Join();

}

}

Информацию о текущем состоянии потока можно получить посредством свойства ThreadState, значением которого являются элементы перечисления ThreadState. Свойство IsAlive позволяет определить, исполняется ли в данный момент поток. Статическое свойство CurrentThread возвращает объект, представляющий текущий поток. Свойство Name служит для установки или чтения строки с именем потока.

Рассмотрим один вспомогательный класс из пространства System.Threading – класс Timer. При помощи этого класса можно организовать вызов определенного метода через указанный промежуток времени.

using System;

using System.Threading;

class MyApp {

static bool TickNext = true;

static void Main() {

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

TimerCallback callback = new TimerCallback(TickTock);

Timer timer = new Timer(callback, null, 1000, 2000);

Console.ReadLine();

}

static void TickTock(object state) {

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

TickNext = ! TickNext;

}

}

В приведенном примере через 1 секунду после создания (третий параметр в конструкторе Timer) с периодичностью 2 секунды (четвертый параметр конструктора) вызывается метод, заданный делегатом callback (первый параметр конструктора и инициализация делегата). Более подробное описание класса Timer можно найти в соответствующем разделе документации SDK.