Потоки
Характеристики точки входа дополнительного потока
Сигнатура точки входа в поток определяется характеристиками класса-делегата:
public delegate void ThreadStart();
Класс-делегат здесь С ПУСТЫМ СПИСКОМ параметров! Очевидно, что точка входа обязана соответствовать этой спецификации. У функции, представляющей точку входа, должен быть ТОТ ЖЕ САМЫЙ список параметров! То есть ПУСТОЙ.
Пустой список параметров функции, представляющей точку входа потока, – это не самое страшное ограничение. Если учесть то обстоятельство, что создаваемый поток не является первичным потоком, то это означает, что вся необходимая входная информация может быть получена заранее и представлена в классе в доступном для функций – членов данного класса виде, то для функции, представляющей точку входа, не составит особого труда эту информацию получить! А зато можно обойтись минимальным набором функций, обслуживающих данный поток.
Запуск вторичных потоков
Пример очень простой: это всего лишь запуск пары потоков. Вторичные потоки запускаются последовательно из главного потока. А уже последовательность выполнения этих потоков определяется планировщиком.
Вторичный поток также вправе поинтересоваться о собственном имени. Надо всего лишь расположить этот код в правильном месте. И чтобы он выполнился в правильное время:
using System; using System.Threading; namespace ThreadApp_1 { // Рабочий класс. Делает себе свое ДЕЛО... class Worker { int allTimes; int n; // Конструктор умолчания... public Worker() { n = 0; allTimes = 0; } // Конструктор с параметрами... public Worker(int nKey, int tKey) { n = nKey; allTimes = tKey; } // Тело рабочей функции... public void DoItEasy() {//==================================== int i; for (i = 0; i < allTimes; i++) { if (n == 0) Console.Write("{0,25}\r",i); else Console.Write("{0,10}\r",i); } Console.WriteLine("\nWorker was here!"); }//==================================== } class StartClass { static void Main(string[] args) { Worker w0 = new Worker(0,100000); Worker w1 = new Worker(1,100000); ThreadStart t0, t1; t0 = new ThreadStart(w0.DoItEasy); t1 = new ThreadStart(w1.DoItEasy); Thread th0, th1; th0 = new Thread(t0); th1 = new Thread(t1); // При создании потока не обязательно использовать делегат. // Возможен и такой вариант. Главное — это сигнатура функции. // th1 = new Thread(w1.DoItEasy); th0.Start(); th1.Start(); } } }Листинг 15.3.
Важно! Первичный поток ничем не лучше любых других потоков приложения. Он может скоропостижно завершиться раньше всех им же порожденных потоков! Приложение же завершается после выполнения ПОСЛЕДНЕЙ команды в ПОСЛЕДНЕМ выполняемом потоке. Неважно, в каком.
Приостановка выполнения потока
Обеспечивается статическим методом Sleep(). Метод статический – это значит, что всегда производится не "усыпление", а "САМОусыпление" выполняемого в данный момент потока. Выполнение методов текущего потока блокируется на определенные интервалы времени. Все зависит от выбора перегруженного варианта метода. Планировщик потоков смотрит на поток и принимает решение относительно того, можно ли продолжить выполнение усыпленного потока.
В самом простом случае целочисленный параметр определяет временной интервал блокировки потока в миллисекундах.
Если значение параметра установлено в 0, поток будет остановлен до того момента, пока не будет предоставлен очередной интервал для выполнения операторов потока.
Если значение интервала задано с помощью объекта класса TimeSpan, то момент, когда может быть возобновлено выполнение потока, определяется с учетом закодированной в этом объекте информации:
// Поток заснул на 1 час, 2 минуты, 3 секунды: Thread.Sleep(new TimeSpan(1,2,3)); ::::: // Поток заснул на 1 день, 2 часа, 3 минуты, 4 секунды, 5 миллисекунд: Thread.Sleep(new TimeSpan(1,2,3,4,5));
Значение параметра, представленное выражением
System.Threading.Timeout.Infinite
позволяет усыпить поток на неопределенное время. А разбудить поток при этом можно с помощью метода Interrupt(), который в этом случае вызывается из другого потока:
using System; using System.Threading; class Worker { int allTimes; // Конструктор с параметрами... public Worker(int tKey) { allTimes = tKey; } // Тело рабочей функции... public void DoItEasy() {//==================================== int i; for (i = 0; i < allTimes; i++) { Console.Write("{0}\r",i); if (i == 5000) { try { Console.WriteLine("\nThread go to sleep!"); Thread.Sleep(System.Threading.Timeout.Infinite); } catch (ThreadInterruptedException e) { Console.WriteLine("{0}, {1}",e,e.Message); } } } Console.WriteLine("\nWorker was here!"); }//==================================== } class StartClass { static void Main(string[] args) { Worker w0 = new Worker(10000); ThreadStart t0; t0 = new ThreadStart(w0.DoItEasy); Thread th0; th0 = new Thread(t0); th0.Start(); Thread.Sleep(10000); if (th0.ThreadState.Equals(ThreadState.WaitSleepJoin)) th0.Interrupt(); Console.WriteLine("MainThread was here..."); } }Листинг 15.4.
И всегда надо помнить: приложение выполняется до тех пор, пока не будет выполнен последний оператор последнего потока. И неважно, выполняются ли при этом потоки, "спят" либо просто заблокированы.
Отстранение потока от выполнения
Обеспечивается нестатическим методом Suspend(). Поток входит в "кому", из которой его можно вывести, вызвав метод Resume(). Этот вызов, естественно, должен исходить из другого потока. Если все не отстраненные от выполнения потоки оказались завершены и некому запустить отстраненный поток – приложение в буквальном смысле "зависает". Операторы в потоке могут выполняться, а выполнить их невозможно по причине отстранения потока от выполнения. Вот приложение и зависает...
using System; using System.Threading; public class ThreadWork { public static void DoWork() { for(int i=0; i<10; i++) { Console.WriteLine("Thread – working."); Thread.Sleep(25); } Console.WriteLine("Thread - still alive and working."); Console.WriteLine("Thread - finished working."); } } class ThreadAbortTest { public static void Main() { ThreadStart myThreadDelegate = new ThreadStart(ThreadWork.DoWork); Thread myThread = new Thread(myThreadDelegate); myThread.Start(); Thread.Sleep(10); Console.WriteLine("Main - aborting my thread."); myThread.Suspend(); Console.WriteLine("Main ending."); } }
Ко всему прочему следует иметь в виду, что метод Suspend() уже устарел...
"System.Threading.Thread.Suspend() is obsolete: Thread.Suspend has been deprecated. Please use other classes in System.Threading such as Monitor, Mutex, Event, and Semaphore to synchronize thread or project resources..."
Не следует использовать методы Suspend и Resume для синхронизации активности потоков. Просто в принципе ничего нельзя будет сделать, когда код в выполняемом потоке будет приостановлен с помощью метода Suspend(). Одним из нежелательных последствий подобного устранения от выполнения может оказаться взаимная блокировка потоков. Deadlocks can occur very easily.