Наименование метода |
Описание |
|
|
|
|
|
завершенности потока следует |
|
|
опрашивать его свойство |
|
|
ThreadState4 |
|
|
|
|
Interrupt |
Метод, который прерывает поток на |
|
некоторое время5 |
||
|
||
|
|
|
|
Метод блокирует выполнение |
|
Join |
вызвавшего его потока до тех пор, |
|
пока не завершится поток, для |
||
|
||
|
которого был вызван данный метод6 |
|
|
|
|
Start |
Метод запускает поток7 |
|
|
|
|
|
Используем вышеописанные свойства и методы для получения |
||
информации о потоке: |
|
|
||
|
|
Листинг 18.1 – Получение информации о потоке |
||
|
|
|
|
|
|
1 |
using System.Threading; |
|
|
|
2 |
using System.Windows.Forms; |
|
|
|
3 |
namespace WindowsFormsApp13 |
|
|
|
4 |
{ |
|
|
|
5 |
public partial class Form1 : Form |
|
|
|
6 |
{ |
|
|
|
7 |
public Form1() |
|
|
|
8 |
{ |
|
|
|
9 |
InitializeComponent(); |
|
|
|
10 |
Thread t = Thread.CurrentThread; |
|
|
|
11 |
t.Name = "Метод Main"; |
|
|
|
12 |
richTextBoxThreadInfo.Text = $"Имя потока: {t.Name}" + |
|
|
|
|
"\n" + |
|
|
|
13 |
$"Запущен ли поток: {t.IsAlive}" + "\n" + |
|
|
|
14 |
$"Приоритет потока: {t.Priority}" + "\n" + |
|
|
|
15 |
$"Статус потока: {t.ThreadState}" + "\n" + |
|
|
|
16 |
$"Домен |
приложения: |
|
|
|
{Thread.GetDomain().FriendlyName}"; |
|
|
4https://docs.microsoft.com/ru-ru/dotnet/api/system.threading.thread.abort?view=net-5.0
5https://docs.microsoft.com/ru-ru/dotnet/api/system.threading.thread.interrupt?view=net-5.0
6https://docs.microsoft.com/ru-ru/dotnet/api/system.threading.thread.join?view=net-5.0
7https://docs.microsoft.com/ru-ru/dotnet/api/system.threading.thread.start?view=net-5.0
6
17}
18}
19}
Вэтом случае мы получим примерно следующий вывод:
Рисунок 18.1 – Вывод состояния потока
Статусы потока содержатся в перечислении ThreadState:
|
Таблица 18.3 – Статусы потока |
|
|
|
|
Статус потока |
Описание статуса |
|
|
|
|
Aborted |
поток остановлен, но пока еще окончательно не |
|
завершен |
||
|
||
|
|
|
AbortRequested |
для потока вызван метод Abort, но остановка потока |
|
еще не произошла |
||
|
||
|
|
|
Background |
поток выполняется в фоновом режиме |
|
|
|
|
Running |
поток запущен и работает (не приостановлен) |
|
|
|
|
Stopped |
поток завершен |
|
|
|
|
StopRequested |
поток получил запрос на остановку |
|
|
|
|
Suspended |
поток приостановлен |
|
|
|
|
SuspendRequested |
поток получил запрос на приостановку |
|
|
|
|
Unstarted |
поток еще не был запущен |
|
|
|
|
WaitSleepJoin |
поток заблокирован в результате действия методов |
|
Sleep или Join |
||
|
||
|
|
В процессе работы потока его статус многократно может измениться под действием методов. Так, в самом начале еще до применения метода Start его статус имеет значение Unstarted. Запустив поток, мы изменим его статус на
Running. Вызвав метод Sleep, статус изменится на WaitSleepJoin. А
применяя метод Abort, мы тем самым переведем поток в состояние
7
AbortRequested, а затем Aborted, после чего поток окончательно завершится.
Когда в программе фигурирует несколько потоков, выбор процессором следующего потока для выполнения не является случайным. Дело в том, что у каждого потока имеется значение приоритета, чем выше приоритет потока,
тем важнее для процессора предоставить время и ресурсы для выполнения именно ему. Если же приоритет потока не слишком высокий, значит он может и подождать в очереди, пока выполняются более приоритетные потоки.
Приоритеты потоков располагаются в перечислении ThreadPriority:
Lowest – самый низкий
BelowNormal – ниже среднего
Normal – стандартный
AboveNormal – выше среднего
Highest – самый высокий
По умолчанию потоку задается значение Normal. Однако мы можем изменить приоритет в процессе работы программы. Например, повысить важность потока, установив приоритет Highest. Среда CLR будет считывать и анализировать значения приоритета и на их основании выделять данному потоку то или иное количество времени.
§18.2.2 Создание потоков
Используя класс Thread, мы можем выделить в приложении несколько потоков, которые будут выполняться одновременно.
Во-первых, для запуска нового потока нам надо определить задачу в приложении, которую будет выполнять данный поток. Для этого мы можем добавить новый метод, производящий какие-либо действия.
Для создания нового потока используется делегат ThreadStart,
который получает в качестве параметра выполняемый метод. И чтобы запустить поток, вызывается метод Start.
Рассмотрим пример:
8
1 |
using System.Threading; |
|
2 |
class Program |
|
3 |
{ |
|
4 |
static void Main(string[] args) |
|
5 |
{ |
|
6 |
// создаем новый поток |
|
7 |
Thread myThread = new Thread(new |
|
ThreadStart(Count)); |
||
|
||
8 |
myThread.Start(); // запускаем поток |
|
9 |
for (int i = 1; i < 9; i++) |
|
10 |
{ |
|
11 |
Console.WriteLine("Главный поток:"); |
|
12 |
Console.WriteLine(i * i); |
|
13 |
Thread.Sleep(300); |
|
14 |
} |
|
15 |
Console.ReadLine(); |
|
16 |
} |
|
17 |
public static void Count() |
|
18 |
{ |
|
19 |
for (int i = 1; i < 9; i++) |
|
20 |
{ |
|
21 |
Console.WriteLine("Второй поток:"); |
|
22 |
Console.WriteLine(i + i); |
|
23 |
Thread.Sleep(400); |
|
24 |
} |
|
25 |
} |
|
26 |
} |
Здесь новый поток будет производить действия, определенные в методе
Count. В данном случае это возведение в квадрат числа и вывод его на экран.
И после каждого умножения с помощью метода Thread.Sleep мы усыпляем поток на 400 миллисекунд.
Чтобы запустить этот метод в качестве второго потока, мы сначала создаем объект потока:
Thread myThread = new Thread(new ThreadStart(Count));.
В конструктор передается делегат ThreadStart, который в качестве параметра принимает метод Count. И следующей строкой
myThread.Start()
9
мы запускаем поток. После этого управление передается главному потоку, и
выполняются все остальные действия, определенные в методе Main.
Таким образом, в нашей программе будут работать одновременно главный поток, представленный методом Main, и второй поток. Кроме действий по созданию второго потока, в главном потоке также производятся некоторые вычисления. Как только все потоки отработают, программа завершит свое выполнение.
Подобным образом мы можем создать и три, и четыре, и целый набор новых потоков, которые смогут решать те или иные задачи.
Существует еще одна форма создания потока:
Thread myThread = new Thread(Count);
Хотя в данном случае явным образом мы не используем делегат
ThreadStart, но неявно он создается. Компилятор C# выводит делегат из сигнатуры метода Count и вызывает соответствующий конструктор.
§18.2.3 Примеры работы потоков Пример 1
На примере посмотрим, на что влияет приоритетность потоков в C#. Мы решили взять для примера программу с тремя потоками, каждый из которых будет выводить в консоль цифры от 0 до 9, от 10 до 19 и от 20 до 29
соответственно. Поставим перед собой задачу вывести в консоль все эти числа последовательно от 0 до 29.
Если мы пренебрежем приоритетами при постановке данной задачи, то у нас никак не получится вывести все наши числа по очереди. Так как у всех трёх потоков одинаковый приоритет, процессору, по сути, будет всё равно,
какой за каким потоки выводить, и у нас частенько будет выходить плохо отсортированный набор чисел.
Попробуем реализовать всё так, чтобы у нас получился необходимый нам результат.
Листинг 18.2 – Изменение приоритета потоков
10