Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Потоки.doc
Скачиваний:
10
Добавлен:
24.12.2018
Размер:
449.02 Кб
Скачать

Создание нескольких потоков в конструкторе класса и синхронизация их завершения.

В классе Thread определено свойство Name, которое хранит имя потока:

public string Name { get; set; }

Первый способ синхронизации завершения.

В классе Thread свойство IsAlive возвращает значение true, если поток, для которого оно опрашивается, еще выполняется. В противном случае оно возвращает значение false.

// Используем свойство IsAlive для установления факта

// завершения выполнения нескольких потоков

using System;

using System.Threading;

class Class1

{

public int count; // количество циклов ожидания

public Thread thrd;

private char id; // идентификатор потока

public Class1(string name, char idt)

{

count = 0;

id = idt;

thrd = new Thread (new ThreadStart ( this.Run) );

thrd.Name = name; // имя потока

thrd.Start();

}

// Входная точка потока.

void Run()

{

Console.WriteLine(thrd.Name + " стартовал.");

do

{

Console.Write (id);

Thread.Sleep(300);

}

while (++count < 10);

Console.WriteLine("\n" + thrd.Name + " завершен.");

}

}

class MoreThreads

{

public static void Main()

{

Console.WriteLine("Основной поток стартовал.");

// Создаем три потока.

Class1 mt1 = new Class1("Дочерний поток 1", '#');

Class1 mt2 = new Class1("Дочерний поток 2", '*');

Class1 mt3 = new Class1("Дочерний поток 3", '-');

do

{

Console.Write(".");

Thread.Sleep(200);

} while (mt1.thrd.IsAlive && mt2.thrd.IsAlive && mt3.thrd.IsAlive);

Console.WriteLine("\n Основной поток завершен.");

}

}

Второй способ состоит в вызове метода Join(), который переводит поток, в котором он был вызван, в состояние ожидания завершения объектного потока.

class MoreThreads

{

public static void Main()

{

Console.WriteLine("Основной поток стартовал.");

// Создаем три потока.

MyThread mt1 = new MyThread("Дочерний поток 1", '#');

MyThread mt2 = new MyThread("Дочерний поток 2", '*');

MyThread mt3 = new MyThread("Дочерний поток 3", '-');

mt1.thrd.Join();

Console.WriteLine("\nДочерний поток 1 присоединен.");

mt2.thrd.Join();

Console.WriteLine("Дочерний поток 2 присоединен.");

mt3.thrd.Join();

Console.WriteLine("Дочерний поток 3 присоединен.");

Console.WriteLine("\nОсновной поток завершен.");

}

}

Свойство IsBackground

Среда .NET Framework определяет два типа потоков: высокоприоритетные и фоновые.

Фоновые потоки заканчиваются автоматически после завершения всех высокоприоритетных потоков.

Чтобы перевести поток в категорию фоновых, достаточно присвоить свойству IsBackground значение true. Значение false означает, что соответствующий поток является высоко­приоритетным.

Формат свойства:

public bool IsBackground { get; set; }

Приоритеты потоков

После старта дочерний поток получает стандартное значение приоритета. Его можно изменить с помощью свойства Priority, которое является членом класса Thread.

Общий формат его использования таков:

public ThreadPriority Priority { get; set; }

Здесь ThreadPriority — перечисление, которое определяет следующие пять значений приоритета:

ThreadPriority.Highest

ThreadPriority.AboveNormal

ThreadPriority.Normal

ThreadPriority.BelowNormal

ThreadPriority.Lowest

По умолчанию потоку присваивается значение приори­тета ThreadPriority.Normal.

Рассмотрим на примере, какой поток быстрее увеличит в цикле переменную count до 1 000 000 000.

// Демонстрация использования приоритетов потоков.

using System;

using System.Threading;

class Class1

{

public int count;

public Thread thrd;

static bool stop = false;

static string currentName;

// Создаем новый поток.

//Этот конструктор не запускает потоки на выполнение. */

public Class1(string name)

{

count = 0;

thrd = new Thread(new ThreadStart(this.Run));

thrd.Name = name;

currentName = name;

}

void Run()

{

Console.WriteLine("Поток " + thrd.Name + " стартовал");

do

{

count++;

if (currentName != thrd.Name)

{

currentName = thrd.Name;

Console.WriteLine ("Выполняется поток " + currentName);

}

} while (stop == false && count < 1000000000);

stop = true;

Console.WriteLine("Поток " + thrd.Name + " завершен");

}

}

class PriorityDemo

{

public static void Main()

{

Class1 mt1 = new Class1("с высоким приоритетом");

Class1 mt2 = new Class1("с низким приоритетом");

// Устанавливаем приоритеты.

mt1.thrd.Priority = ThreadPriority.AboveNormal;

mt2.thrd.Priority = ThreadPriority.BelowNormal;

// Запускаем потоки на выполнение.

mt1.thrd.Start();

mt2.thrd.Start();

mt1.thrd.Join();

mt2.thrd.Join();

Console.WriteLine();

Console.WriteLine("Поток " + mt1.thrd.Name + " досчитал до " + mt1.count);

Console.WriteLine("Поток " + mt2.thrd.Name + " досчитал до " + mt2.count);

}

}

Приостановка, возобновление и завершение

выполнения потоков

public void Suspend () – приостановка выполнения потока до возобновления.

public void Resume () – возобновление выполнения потока.

public void Abort() - завершить поток.

Метод Abort() генерирует исключение типа ThreadAbortException для потока, из которого этот метод вызван.

Метод Abort() не всегда способен немедленно остановить выполнение потока, поэтому, если важно, чтобы поток был завершен до продолжения вашей программы, необходимо сопроводить вызов метода Abort() вызовом метода Join().

// Приостановка, возобновление и завершение потока.

using System;

using System.Threading;

class Class1

{

public Thread thrd;

public MyThread(string name)

{

thrd = new Thread(new ThreadStart(this.Run));

thrd.Name = name;

thrd.Start();

}

// Это входная точка для потока,

void Run ()

{

Console.WriteLine (thrd.Name + " стартовал.");

for (int i = 1; i <= 1000; i++)

{

Console.Write (i + " ");

if ((i % 10) == 0)

{

Console.WriteLine();

Thread.Sleep(250);

}

}

Console.WriteLine ( thrd.Name + " завершен." );

}

}

class SuspendResumeStop

{

public static void Main()

{

MyThread mtl = new Class1 ("Мой поток");

Thread.Sleep(1000); // Разрешаем стартовать

// дочернему потоку.

mtl.thrd.Suspend();

Console.WriteLine("Приостановка выполнения потока.");

Thread.Sleep(1000);

mtl.thrd.Resume();

Console.WriteLine("Возобновление выполнения потока.");

Thread.Sleep(1000);

mtl.thrd.Suspend();

Console.WriteLine("Приостановка выполнения потока.");

Thread.Sleep(1000);

mtl.thrd.Resume();

Console.WriteLine("Возобновление выполнения потока.");

Thread.Sleep(1000);

Console.WriteLine("Завершение выполнения потока.");

mtl.thrd.Abort();

mtl.thrd.Join(); // Ожидаем завершения выполнения потока

Console.WriteLine("Основной поток завершен.");

}

}

Результаты выполнения этой программы:

Мой поток стартовал.

123456789 10

11 12 13 14 15 16 17 18 19 20

21 22 23 24 25 26 27 28 29 30

31 32 33 34 35 36 37 38 39 40

Приостановка выполнения потока.

41 42 43 44 45 46 47 48 49 50

Возобновление выполнения потока.

51 52 53 54 55 56 57 58 59 60

61 62 63 64 65 66 67 68 69 70

71 72 73 74 75 76 77 78 79 80

Приостановка выполнения потока.

81 82 83 84 85 86 87 88 89 90

Возобновление выполнения потока.

91 92 93 94 95 96 97 98 99 100

101 102 103 104 105 106 107 108 109 110

111 112 113 114 115 116 117 118 119 120

Завершение выполнения потока.

Основной поток завершен.

Альтернативный формат метода Abort()

public void Abort (object info)

Здесь info содержит информацию, которую необходимо передать в поток при его завершении. Эта информация доступна через свойство ExceptionState класса ThreadAbortException.

Этот формат можно использовать для передачи потоку кода завершения.

// Использование метода Abort(object).

using System;

using System.Threading;

class Class1

{

public Thread thrd;

public Class1(string name)

{

thrd = new Thread(new ThreadStart(this.Run));

thrd.Name = name;

thrd.Start();

}

// Это входная точка для потока

void Run()

{

try

{

Console.WriteLine(thrd.Name + " стартовал.");

for (int i = 1; i <= 1000; i++)

{

Console.Write(i + " ");

if ((i % 10) == 0)

{

Console.WriteLine();

Thread.Sleep(250);

}

}

Console.WriteLine(thrd.Name +

"завершился нормально.");

}

catch (ThreadAbortException exc)

{

Console.WriteLine(

"Выполнение потока прервано, код завершения = " +

exc.ExceptionState);

}

}

}

class UseAltAbort

{

public static void Main()

{

Class1 mtl = new Class1("Мой поток");

Thread.Sleep(1000); // Разрешаем стартовать

// дочернему потоку.

Console.WriteLine("Прерывание выполнения потока.");

mtl.thrd.Abort(100);

mtl.thrd.Join(); // Ожидаем завершения

// выполнения потока.

Console.WriteLine("Основной поток завершен.");

}

}

Результаты выполнения:

Мой поток стартовал.

123456789 10

11 12 13 14 15 16 17 18 19 20

21 22 23 24 25 26 27 28 29 30

31 32 33 34 35 36 37 38 39 40

Прерывание выполнения потока.

41 42 43 44 45 46 47 48 49 50

Выполнение потока прервано, код завершения = 100

Основной поток завершен.

Отмена действия метода Abort()

Поток может отменить запрос на завершение выполнения. Для этого поток должен перехватить исключение типа ThreadAbortException, а затем вызвать метод ResetAbort(). Это защищает исключение от автоматической регенерации по окончании его обработки потоком.

public static void ResetAbort()

// Использование метода ResetAbort().

using System;

using System.Threading;

class Class1

{

public Thread thrd;

public Class1(string name)

{

thrd = new Thread(new ThreadStart(this.Run));

thrd.Name = name;

thrd.Start();

}

// Это входная точка класса для потока,

void Run()

{

Console.WriteLine (thrd.Name + " стартовал.");

for (int i = 1; i <= 1000; i++)

{

try

{

Console.Write (i + " ");

if ((i % 10) == 0)

{

Console.WriteLine();

Thread.Sleep(250);

}

}

catch (ThreadAbortException exc)

{

if ((int)exc.ExceptionState == 0)

{

Console.WriteLine("Прерывание отменено! Код завершения = "

+ exc.ExceptionState);

Thread.ResetAbort();

}

else

Console.WriteLine(

"Выполнение потока прервано, код завершения = "

+ exc.ExceptionState);

}

}

Console.WriteLine(thrd.Name + " завершился нормально.");

}

}

class ResetAbort

{

public static void Main()

{

Class1 mtl = new Class1("Мой поток");

Thread.Sleep(1000); // Разрешаем стартовать

// дочернему потоку.

Console.WriteLine("Прерывание выполнения потока.");

mtl.thrd.Abort(0); // Это не остановит выполнение потока.

Thread.Sleep(1000); // Разрешаем дочернему потоку

// поработать немного дольше.

Console.WriteLine("Прерывание выполнения потока.");

mtl.thrd.Abort(100); // Эта инструкция в состоянии

// остановить выполнение потока.

mtl.thrd.Join(); // Ожидаем завершения

// выполнения потока.

Console.WriteLine("Основной поток завершен.");

}

}

Результаты выполнения этой программы таковы:

Мой поток стартовал.

123456789 10

11 12 13 14 15 16 17 18 19 20

21 22 23 24 25 26 27 28 29 30

31 32 33 34 35 36 37 38 39 40

Прерывание выполнения потока.

Прерывание отменено! Код завершения = 0

41 42 43 44 45 46 47 48 49 50

51 52 53 54 55 56 57 58 59 60

61 62 63 64 65 66 67 68 69 70

71 72 73 74 75 76 77 78 79 80

Прерывание выполнения потока.

Выполнение потока прервано, код завершения = 100

Основной поток завершен.

Вы убедились, что, если метод Abort() вызывается с аргументом, значение которого равно нулю, то посредством вызова метода ResetAbort() запрос на прерывание отменяется, и поток продолжает выполняться. Любое другое значение этого аргумента останавливает выполнение потока.

Определение состояния потока

Состояние потока можно получить из свойства ThreadState, определенного в классе Thread:

public Thread ThreadState{ get; }

Состояние потока возвращается как значение, определенное перечислением ThreadState. В нем определены такие значения:

ThreadState.Aborted ThreadState.AbortRequested

ThreadState.Background ThreadState.Running

ThreadState.Stopped ThreadState.StopRequested

ThreadState.Suspended ThreadState.SuspendRequested

ThreadState.Unstarted ThreadState.WaitSleepJoin

В состояние, представленное значением ThreadState.WaitSleepJoin, поток входит, ожидая результатов вызова метода Wait(), Sleep() или Join().

Использование основного потока

Основной поток обрабатывается подобно всем остальным потокам.

Чтобы получить доступ к основному потоку, необходимо иметь объект класса Thread, который его представляет. Это реализуется с помощью свойства CurrentThread, которое является членом класса Thread. Формат его использования таков:

public static Thread CurrentThread { get; }

Это свойство возвращает ссылку на поток, в котором оно опрашивается.

Имея такую ссылку, можно управлять основным потоком, как и любым другим.

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

// Управление основным потоком.

using System;

using System.Threading;

class UseMain

{

public static void Main()

{

Thread thrd;

// Получаем ссылку на объект основного потока

thrd = Thread.CurrentThread;

// Отображаем имя основного потока

if (thrd.Name == null)

Console.WriteLine("Основной поток не имеет имени.");

else

Console.WriteLine("Имя основного потока: " + thrd.Name);

// Отображаем приоритет основного потока.

Console.WriteLine("Приоритет: " + thrd.Priority);

Console.WriteLine();

// Задаем имя и приоритет.

Console.WriteLine("Установка имени и приоритета.\n");

thrd.Name = "Основной поток";

thrd.Priority = ThreadPriority.AboveNormal;

Console.WriteLine(

"У основного потока теперь есть имя: " + thrd.Name);

Console.WriteLine("Приоритет теперь таков: " + thrd.Priority);

}

}

Результаты выполнения этой программы таковы:

Основной поток не имеет имени.

Приоритет: Normal

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

У основного потока теперь есть имя: Основной поток

Приоритет теперь таков: AboveNormal

Предупреждение: будьте осторожны при выполнении операций над основным потоком. Например, если обращение к методу Join()

thrd.Join();

добавить в конец метода Main(), программа никогда не завершится, поскольку она будет ожидать, пока не завершится основной поток!

Использование класса Timer для управления потоками

Timer – члены класса

Открытые конструкторы

Timer - конструктор

Перегружен. Инициализирует новый экземпляр класса Timer.

Открытые методы

Change

Перегружен. Меняет время запуска и интервал межу вызовами метода таймера.

CreateObjRef

(унаследовано от MarshalByRefObject)

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

Dispose

Перегружен. Освобождает все ресурсы, используемые экземпляром класса Timer.

Equals

(унаследовано от Object)

Перегружен. Определяет, равны ли два экземпляра Object.

GetHashCode

(унаследовано от Object)

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

GetLifetimeService (унаследовано от MarshalByRefObject)

Извлекает служебный объект текущего срока действия, который управляет средствами срока действия данного экземпляра.

GetType (унаследовано от Object)

Возвращает Type текущего экземпляра.

InitializeLifetimeService

(унаследовано от MarshalByRefObject)

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

ToString (унаследовано от Object)

Возвращает String, который представляет текущий Object.

Защищенные методы

Finalize

Переопределен. Освобождает ресурсы, удерживаемые текущим экземпляром.

В языках C# и C++ для функций финализации используется синтаксис деструктора.

MemberwiseClone

(унаследовано от Object)

Создает неполную копию текущего Object.

Timer - конструктор

Инициализирует новый экземпляр класса Timer.

Список перегрузки

1. Инициализирует новый экземпляр класса Timer, используя указанное 32-разрядное знаковое целое число для задания временного интервала.

public Timer(TimerCallback, object, int, int);

2. Инициализирует новый экземпляр класса Timer, используя указанное 64-разрядное знаковое целое число для измерения временных интервалов.

public Timer(TimerCallback, object, long, long);

3. Инициализирует новый экземпляр класса Timer, используя значения объекта TimeSpan для измерения временных интервалов.

public Timer(TimerCallback, object, TimeSpan, TimeSpan);

4. Инициализирует новый экземпляр класса Timer, используя указанное 32-разрядное беззнаковое целое число для измерения временных интервалов. Этот конструктор не соответствует спецификации CLS.

public Timer(TimerCallback, object, uint, uint);

Timer (TimerCallback, Object, Int32, Int32)

Инициализирует новый экземпляр класса Timer, используя указанное 32-разрядное знаковое целое число для задания временного интервала.

public Timer ( TimerCallback callback,

object state,

int dueTime,

int period );

Параметры:

callback - делегат TimerCallback, представляющий выполняемый метод.

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

dueTime - количество времени до начала использования параметра callback, в миллисекундах. Следует задать поле Timeout.Infinite для того, чтобы не допустить запуск таймера. Задайте значение ноль (0) для немедленного запуска таймера.

period - временной интервал между вызовами параметра callback, в миллисекундах. Следует задать поле Timeout.Infinite для отключения периодического сигнализирования.

Исключения

Тип исключения

Условие

ArgumentOutOfRangeException

Значение параметра dueTime или period отрицательно, и не равно Infinite.

ArgumentNullException

Параметр callback имеет значение пустая ссылка

Примечания

Делегат, заданный параметром callback, вызывается один раз, после того, как пройдет время, равное dueTime, и затем каждый раз по прошествии времени, равного значению period.

Если параметр dueTime равен нулю (0), сразу используется параметр callback. Если параметр dueTime равен Timeout.Infinite, параметр callback не используется; счетчик отключается, но он может быть включен заново вызовом метода Change.

Если параметр period равен нулю (0) или Infinite, а параметр dueTime не равен Infinite, параметр callback используется один раз; периодическое поведение таймера отключается, но может быть включено заново используя метод Change.

Пример

using System;

using System.Threading;

class ManagementThreads

{

// Метод CheckTime вызывается по таймеру

public static void CheckTime(Object state)

{

Console.WriteLine(DateTime.Now);

}

static void Main()

{

// Создание делегата, который будет вызываться таймером

TimerCallback tc = new TimerCallback (CheckTime);

// Создание таймера, срабатывающего дважды в секунду (500)

// Первый запуск произойдет через одну секунду (1000)

Timer t = new Timer (tc, null, 1000, 500);

// Ожидание ввода пользователя

Console.WriteLine ("Нажмите Enter для выхода");

Console.ReadLine();

t.Dispose(); // Освобождение ресурсов

t = null;

}

}

Совет по созданию многопоточных программ

Если создать слишком много потоков, реальное быстродействие программы может пострадать.

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

Библиотека классов .NET Framework