Потоки
Характеристики точки входа дополнительного потока
Сигнатура точки входа в поток определяется характеристиками класса-делегата:
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.