Процесс – объект, который создается Операционной Системой для приложения (не для приложения .NET) в момент его запуска. Характеризуется собственным адресным пространством, которое напрямую недоступно другим процессам.
Поток. В рамках процесса создаются потоки (один – первичный – создается всегда). Это последовательность выполняемых команд процессора. В приложении может быть несколько потоков (первичный поток и дополнительные потоки).
Потоки в процессе разделяют совместно используемые данные и имеют собственные стеки вызовов и
Общее управление выполнением потоков осуществляется на уровне ОС. Нет ничего, кроме выполняющихся потоков, которыми руководит управляющий поток ОС. В этом управляющем потоке принимается решение относительно того, на протяжении какого времени будет выполняться данный поток. Управляющий поток ОС прерывает выполнение текущего потока, состояние прерванного потока фиксируется в TLS, после чего управление передается другому потоку.
В приложении все потоки выполняются в рамках
Для обеспечения взаимодействия потоков в разных процессах используются каналы, обмен информацией через которые обеспечивается специальными системными средствами.

МНОГОПОТОЧНАЯ ОС. Прежде всего операционная система должна допускать параллельную (псевдопараллельную) работу нескольких программ.
Многопоточное приложение: отдельные компоненты работают одновременно (псевдоодновременно), не мешая друг другу.
Случаи использования многопоточности:
Виды многопоточности:
Выполнение приложений .NET начинается с запуска .NET Framework. Это процесс со своими потоками, специальными атрибутами и правилами взаимодействия с другими процессами. Приложения .NET выполняются в ОДНОМ процессе. Для этих приложений процесс .NET Framework играет роль, аналогичную роли операционной системы при обеспечении выполнения процессов.
Выполняемые в процессе .NET Framework .NET-приложения также изолируются друг от друга. Средством изоляции .NET-приложений являются домены приложений.

Функциональность и основные свойства домена приложения реализуются в классе System.AppDomain.
| Методы класса System.AppDomain | Описание |
|---|---|
CreateDomain() |
Статический. Создает новый |
GetCurrentThreadId() |
Статический. Возвращает Id текущего потока |
Unload() |
Статический. Для выгрузки из процесса указанного домена приложения |
BaseDirectory |
Свойство. Возвращает базовый каталог, который используется РАСПОЗНАВАТЕЛЕМ для поиска нужных приложению сборок |
CreateInstance() |
Создает объект заданного типа, определенного в указанной сборке |
ExecuteAssembly() |
Запускает на выполнение сборку, имя которой было указано в качестве параметра |
GetAssemblies() |
Возвращает список сборок, загруженных в текущий |
Load() |
Загружает сборку в текущий |
Таким образом, множество выполняемых .NET-приложений ограничены объектами – представителями класса System.AppDomain.
Домен обеспечивает выполнение приложения. Приложение может "провести инспекцию пограничной службы", то есть получить доступ к объекту-домену.
Пример. Приложение-инспектор. Получает ссылку на текущий
using System;
namespace Domains_00
{
class Class1
{
static void Main(string[] args)
{
AppDomain appD1 = System.AppDomain.CurrentDomain;
AppDomain appD2 = System.Threading.Thread.GetDomain();
if (AppDomain.ReferenceEquals(appD1,appD2))
Console.WriteLine
("The same! {0}, {1}.",appD1.FriendlyName,appD2.FriendlyName);
else
Console.WriteLine
("Different! {0}, {1}.",appD1.FriendlyName,appD2.FriendlyName);
if (null != appD1.DynamicDirectory)
Console.WriteLine("{0}",appD1.DynamicDirectory);
else
Console.WriteLine("No DynamicDirectory.");
if (null != appD1.RelativeSearchPath)
Console.WriteLine("{0}",appD1.RelativeSearchPath);
else
Console.WriteLine("No RelativeSearchPath.");
}
}
}
Таким образом, ссылка на текущий домен может быть получена:
System.AppDomain.CurrentDomain ;System.Threading.Thread.GetDomain():В рамках процесса (процесса .NET Framework) может быть создано множество доменов (объектов-доменов), причем новые домены можно создавать непосредственно в ходе выполнения .NET-приложения. Порождение новых доменов обеспечивается четверкой статических перегруженных методов – членов класса AppDomain с именем CreateDomain.
public static AppDomain CreateDomain(string);
public static AppDomain CreateDomain(string, Evidence);
public static AppDomain CreateDomain(string, Evidence, AppDomainSetup);
public static AppDomain CreateDomain(string, Evidence, string, string, bool);
Прилагаемый пример демонстрирует методы анализа процесса, домена и потоков (в .NET потоки и сборки также представлены объектами соответствующих классов).
Здесь показаны потоки в процессе, а также сборки, которые выполняются в
using System;
using System.Windows.Forms;
// Это пространство имен требуется для работы с классом Assembly.
using System.Reflection;
// Это пространство имен требуется для работы с классом Process.
using System.Diagnostics;
namespace AppDomain1
{
class MyAppDomain
{
public static void ShowThreads()
{
Process proc = Process.GetCurrentProcess();
foreach(ProcessThread aPhysicalThread in proc.Threads)
{
Console.WriteLine
(aPhysicalThread.Id.ToString() + ":" + aPhysicalThread.ThreadState);
}
}
public static void ShowAssemblies()
{
// Получили ссылку на домен.
AppDomain ad = AppDomain.CurrentDomain;
// В рамках домена может быть множество сборок.
// Можно получить список сборок домена.
Assembly[] loadedAssemblies = ad.GetAssemblies();
// У домена имеется FriendlyName, которое ему присваивается
// при создании. При этом у него даже нет доступного конструктора.
Console.WriteLine("Assemblies in {0} domain:", ad.FriendlyName);
foreach(Assembly assembly in loadedAssemblies)
{
Console.WriteLine(assembly.FullName);
}
}
static void Main(string[] args)
{
Console.WriteLine("=========================");
// MessageBox.Show("XXX"); – Это всего лишь вызов метода класса
// MessageBox. Вызов выполняется лишь потому, что в домен с самого
// начала загружается сборка System.Windows.Forms.dll.
MessageBox.Show("XXX");
ShowThreads();
ShowAssemblies();
Console.WriteLine("=========================");
// Даже в таком простом приложении в рамках домена приложения
// живут три сборки!
}
}
}
В этом пространстве объявляются типы, которые используются для создания многопоточных приложений: работа с потоком, средства синхронизации доступа к общим данным, примитивный вариант класса Timer... Много всего!
| Тип | Назначение |
|---|---|
|
Синхронизация доступа к общим данным |
Monitor |
Синхронизация потоковых объектов при помощи блокировок и управления ожиданием |
|
Синхронизация ПРОЦЕССОВ |
Thread |
Собственно класс потока, работающего в среде выполнения .NET. В текущем |
ThreadPool |
Класс, предоставляющий средства управления набором взаимосвязанных потоков |
ThreadStart |
Класс-делегат для метода, который должен быть выполнен перед запуском потока |
Timer |
Вариант класса-делегата, который обеспечивает передачу управления некоторой функции-члену (неважно какого класса!) в указанное время. Сама процедура ожидания выполняется потоком в пуле потоков |
TimerCallback |
Класс-делегат для объектов класса Timer. |
WaitHandle |
Объекты – представители этого класса являются объектами синхронизации (обеспечивают многократное ожидание). |
WaitCallback |
Делегат, представляющий методы для рабочих элементов (объектов) класса ThreadPool |
Thread -класс представляет управляемые потоки: создает потоки и управляет ими — устанавливает приоритет и статус потоков. Это объектная оболочка вокруг определенного этапа выполнения программы внутри домена приложения.
| Статические члены класса Thread | Назначение |
|---|---|
CurrentThread |
Свойство. Только для чтения. Возвращает ссылку на поток, выполняемый в настоящее время |
GetData() SetData() |
Обслуживание слота текущего потока |
GetDomain() GetDomainID() |
Получение ссылки на ID домена), в рамках которого работает указанный поток |
Sleep() |
Блокировка выполнения потока на определенное время |
| Нестатические члены | Назначение |
|---|---|
IsAlive |
Свойство. Если поток запущен, то true |
IsBackground |
Свойство. Работа в фоновом режиме. GC работает как фоновый поток |
Name |
Свойство. Дружественное текстовое имя потока. Если поток никак не назван – значение свойства установлено в null. Поток может быть поименован единожды. Попытка переименования потока возбуждает исключение |
Priority |
Свойство. Значение ThreadPriority |
ThreadState |
Свойство. Состояние потока. Область значений – значения перечисления ThreadState |
Interrupt() |
Прерывание работы текущего потока |
Join() |
Ожидание появления другого потока (или определенного промежутка времени) с последующим завершением |
Resume() |
Возобновление выполнения потока после приостановки |
Start() |
Начало выполнения ранее созданного потока, представленного делегатом класса ThreadStart |
Suspend() |
|
Abort() |
Завершение выполнения потока посредством генерации исключения TreadAbortException в останавливаемом потоке. Это исключение следует перехватывать для продолжения выполнения оставшихся потоков приложения. Перегруженный вариант метода содержит параметр типа object, который может включать дополнительную специфичную для данного приложения информацию. |
Потоки рождаются безымянными. Это означает, что у объекта, представляющего поток, свойство Name имеет значение null. Ничего страшного. Главный поток все равно изначально поименовать некому. То же самое и с остальными потоками. Однако никто не может помешать потоку поинтересоваться своим именем — и получить имя. Несмотря на то, что это свойство при выполнении приложения играет вспомогательную роль, повторное переименование потоков недопустимо. Повторное изменение значения свойства Name приводит к возбуждению исключения.
using System;
using System.Threading;
namespace ThreadApp_1
{
class StartClass
{
static void Main(string[] args)
{//==============================================================
int i = 0;
bool isNamed = false;
do
{
try
{
if (Thread.CurrentThread.Name == null)
{
Console.Write("Get the name for current Thread > ");
Thread.CurrentThread.Name = Console.ReadLine();
}
else
{
Console.WriteLine("Current Thread : {0}.", Thread.CurrentThread.Name);
if (!isNamed)
{
Console.Write("Rename it. Please...");
Thread.CurrentThread.Name = Console.ReadLine();
}
}
}
catch (InvalidOperationException e)
{
Console.WriteLine("{0}:{1}",e,e.Message);
isNamed = true;
}
i++;
}
while (i < 10);
}//=============================================================
}
}
Но сначала о том, что должно происходить в потоке. Выполнение текущего потока предполагает выполнение программного кода.
Что это за код? Это код приложения. Это код статических и нестатических методов – членов классов, которые объявляются или просто используются в приложении.
Запустить поток можно единственным способом: указав точку входа потока – метод, к выполнению операторов которого должен приступить запускаемый поток.
Точкой входа ПЕРВИЧНОГО потока являются СТАТИЧЕСКИЕ функции Main или WinMain. Точнее, первый оператор метода.
Точка входа ВТОРИЧНОГО потока назначается при создании потока.
А дальше – как получится. Поток выполняется оператор за оператором. Со всеми циклами, заходами в вызываемые функции, в блоки свойств, в операторы конструкторов... И так продолжается до тех пор, пока:
Main или WinMain );Поскольку первичный поток создается и запускается автоматически (без какого-либо особого участия со стороны программиста), то и заботиться в случае простого однопоточного приложения не о чем.
При создании многопоточного приложения забота о создании дополнительных потоков – это забота программиста. Здесь все надо делать своими руками.
Деятельность по созданию потока предполагает три этапа:
ThreadStart class ), который настраивается на точку входа в поток;Замечание. Точкой входа в поток не может быть конструктор, поскольку не существует делегатов, которые могли бы настраиваться на конструкторы.
Сигнатура точки входа в поток определяется характеристиками класса-делегата:
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();
}
}
}
Важно! Первичный поток ничем не лучше любых других потоков приложения. Он может скоропостижно завершиться раньше всех им же порожденных потоков! Приложение же завершается после выполнения ПОСЛЕДНЕЙ команды в ПОСЛЕДНЕМ выполняемом потоке. Неважно, в каком.
Обеспечивается статическим методом 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...");
}
}
И всегда надо помнить: приложение выполняется до тех пор, пока не будет выполнен последний оператор последнего потока. И неважно, выполняются ли при этом потоки, "спят" либо просто заблокированы.
Обеспечивается нестатическим методом 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
Не следует использовать методы Suspend и Resume для синхронизации активности потоков. Просто в принципе ничего нельзя будет сделать, когда код в выполняемом потоке будет приостановлен с помощью метода Suspend(). Одним из нежелательных последствий подобного устранения от выполнения может оказаться взаимная блокировка потоков.
return:for (x=0;;x++)
{
if (x==max)
return; // Все. Этот оператор оказался последним.
else
{
::::::::::
}
}
Abort(). Эта остановка является достаточно сложным делом.ThreadAbortException. Естественно, это исключение может быть перехвачено в соответствующем блоке catch. Во время обработки исключения допустимо выполнение самых разных действий, которые осуществляются в этом самом "остановленном" потоке. В том числе возможна и реанимация остановленного потока путем вызова метода ResetAbort().finally, которые выполняются все в том же потоке.Таким образом, остановка потока путем вызова метода Abort не может рассматриваться как НЕМЕДЛЕННАЯ остановка выполнения потока:
using System;
using System.Threading;
public class ThreadWork
{
public static void DoWork()
{
int i;
try
{
for(i=0; i<100; i++)
{
//4. Вот скромненько так работает...
// Поспит немножко – а потом опять поработает.
// Take Your time! 100 раз прокрутиться надо
// вторичному потоку до нормального завершения.
Console.WriteLine("Thread – working {0}.", i);
Thread.Sleep(10);
}
}
catch(ThreadAbortException e)
{
//6.
//– Ну дела! А где это мы...
Console.WriteLine("Thread – caught ThreadAbortException – resetting.");
Console.WriteLine("Exception message: {0}", e.Message);
// (Голос сверху)
//– Вы находитесь в блоке обработки исключения, связанного с
// непредвиденным завершением потока.
//– Понятно... Значит, не успели. "Наверху" сочли нашу деятельность
// нецелесообразной и не дали (потоку) завершить до конца начатое дело!
Thread.ResetAbort();
// (Перехватывают исключение и отменяют остановку потока)
// Будем завершать дела. Но будем делать это как положено,
// а не в аварийном порядке. Нам указали на дверь, но мы
// уходим достойно!
// (Комментарии постороннего)
// А чтобы стал понятен альтернативный исход – надо
// закомментировать строку с оператором отмены остановки потока.
}
finally
{
//7.
//– Вот где бы мы остались, если бы не удалось отменить
// остановку потока! finally блок... Отстой!
Console.WriteLine("Thread – in finally statement.");
}
//8.
// – А вот преждевременный, но достойный уход.
// Мы не довели дело до конца только потому, что нам не дали
// сделать этого. Обстоятельства бывают выше. Уходим достойно.
Console.WriteLine("Thread – still alive and working.");
Console.WriteLine("Thread – finished working.");
}
}
class ThreadAbortTest
{
public static void Main()
{
//1. Мероприятия по организации вторичного потока!
ThreadStart myThreadDelegate = new ThreadStart(ThreadWork.DoWork);
Thread myThread = new Thread(myThreadDelegate);
//2. Вторичный поток стартовал!
myThread.Start();
//3. А вот первичный поток – самоусыпился!
// И пока первичный поток спит, вторичный поток – работает!
Thread.Sleep(50);
//5. Но вот первичный поток проснулся – и первое, что он
// делает, – это прерывает вторичный поток!
Console.WriteLine("Main – aborting my thread.");
myThread.Abort();
//9. А в столицах тоже все дела посворачивали...
Console.WriteLine("Main ending.");
}
}
Несколько потоков выполняются "параллельно" в соответствии с предпочтениями планировщика потоков. Нестатический метод Join() позволяет изменить последовательность выполнения потоков многопоточного приложения. Метод Join() выполняется в одном из потоков по отношению к другому потоку.
В результате выполнения этого метода данный текущий поток немедленно блокируется до тех пор, пока не завершит свое выполнение поток, по отношению к которому был вызван метод Join.
Перегруженный вариант метода имеет целочисленный аргумент, который воспринимается как временной интервал. В этом случае выполнение текущего потока может быть возобновлено по истечении этого периода времени до завершения этого потока:
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(10);
}
Console.WriteLine("Thread – finished working.");
}
}
class ThreadTest
{
public static void Main()
{
ThreadStart myThreadDelegate = new ThreadStart(ThreadWork.DoWork);
Thread myThread = new Thread(myThreadDelegate);
myThread.Start();
Thread.Sleep(100);
myThread.Join(); // Закомментировать вызов метода и осознать разницу.
Console.WriteLine("Main ending.");
}
}
Класс ThreadState определяет набор всех возможных состояний выполнения для потока. После создания потока и до завершения он находится по крайней мере в одном из состояний. Потоки, созданные в общеязыковой среде выполнения, изначально находятся в состоянии Unstarted, в то время как внешние потоки, приходящие в среду выполнения, находятся уже в состоянии Running. Потоки с состоянием Unstarted переходят в состояние Running при вызове метода Start. Не все комбинации значений ThreadState являются допустимыми; например, поток не может быть одновременно в состояниях Aborted и Unstarted.
В следующей таблице перечислены действия, вызывающие смену состояния.
| Действие | Состояние потока |
|---|---|
| Поток создается в среде CLR | Unstarted |
Поток вызывает метод Start |
Running |
| Поток начинает выполнение | Running |
Поток вызывает метод Sleep |
WaitSleepJoin |
Поток вызывает метод Wait для другого объекта |
WaitSleepJoin |
Поток вызывает метод Join для другого потока |
WaitSleepJoin |
Другой поток вызывает метод Interrupt |
Running |
Другой поток вызывает метод Suspend |
SuspendRequested |
Поток отвечает на запрос метода Suspend |
Suspended |
Другой поток вызывает метод Resume |
Running |
Другой поток вызывает метод Abort |
AbortRequested |
Поток отвечает на запрос метода Abort |
Stopped |
| Поток завершен | Stopped |
Начальное состояние потока (если это не главный поток), в котором он оказывается непосредственно после его создания, – Unstarted. В этом состоянии он пребывает до тех пор, пока вызовом метода Start() не будет переведен в состояние Running.
В дополнение к вышеперечисленным состояниям существует также Background – состояние, которое указывает, выполняется ли поток на фоне или на переднем плане.
Свойство Thread.ThreadState потока содержит текущее состояние потока. Для определения текущего состояния потока в приложении можно использовать битовые маски. Пример условного выражения:
if((myThread.ThreadState (ThreadState.Stopped | ThreadState.Unstarted))==0) {...}
Члены перечисления:
| Имя члена | Описание | Значение |
|---|---|---|
Running |
Поток был запущен, он не заблокирован, и нет задерживающегося объекта ThreadAbortException |
0 |
StopRequested |
Поток запрашивается на остановку. Это только для внутреннего использования | 1 |
SuspendRequested |
Запрашивается приостановка работы потока | 2 |
Background |
Поток выполняется как фоновый, что является противоположным к приоритетному потоку. Это состояние контролируется заданием свойства Thread.IsBackground |
4 |
Unstarted |
Метод Thread.Start не был вызван для потока |
8 |
Stopped |
Поток остановлен | 16 |
WaitSleepJoin |
Поток заблокирован в результате вызова к методам Wait, Sleep или Join |
32 |
Suspended |
Работа потока была приостановлена | 64 |
AbortRequested |
Метод Thread.Abort был вызван для потока, но поток еще не получил задерживающийся объект System.Threading.ThreadAbortException, который будет пытаться завершить поток |
128 |
Aborted |
Поток находится в Stopped -состоянии |
256 |
Ниже приводится диаграмма состояний потока.

В условиях многопоточного приложения разные потоки могут переводить друг друга в разные состояния. Таким образом, поток может находиться одновременно БОЛЕЕ ЧЕМ В ОДНОМ состоянии.
Например, если поток блокирован в результате вызова метода Wait, а другой поток вызвал по отношению к блокированному потоку метод Abort, то блокированный поток окажется в одно и то же время в состояниях WaitSleepJoin и AbortRequested.
В этом случае, как только поток выйдет из состояния WaitSleepJoin (в котором он оказался в результате выполнения метода Wait ), ему будет предъявлено исключение ThreadAbortException, связанное с началом процедуры aborting.
С другой стороны, не все сочетания значений ThreadState допустимы. Например, поток не может одновременно находиться в состояниях Aborted и Unstarted. Перевод потока из одного состояния в другое, несовместимое с ним состояние, а также повторная попытка перевода потока в одно и то же состояние (пара потоков один за другим применяют метод Resume() к одному и тому же потоку) может привести к генерации исключения. Поэтому операторы, связанные с управлением потоками, следует размещать в блоках try.
Информация о возможности одновременного пребывания потока в нескольких состояниях представлена в таблице допустимых состояний:
| AR | Ab | Back | U | S | R | W | St | SusR | StopR | |
|---|---|---|---|---|---|---|---|---|---|---|
Abort Requested |
— | N | Y | Y | Y | N | Y | N | Y | N |
Aborted |
N | — | Y | N | N | N | N | N | N | N |
Background |
Y | Y | — | Y | Y | N | Y | Y | Y | N |
Unstarted |
Y | N | Y | — | N | N | N | N | N | N |
Suspended |
Y | N | Y | N | — | N | Y | N | N | N |
Running WaitSleep |
N | N | N | N | N | — | N | N | N | N |
Join |
Y | N | Y | N | Y | N | — | N | Y | N |
Stopped |
N | N | Y | N | N | N | N | — | Y | N |
Suspend Requested |
Y | N | Y | N | N | N | Y | N | — | N |
Stop Requested |
N | N | N | N | N | N | N | N | N | — |
Потоки выполняются:
Foreground threads ) иBackground threads ).Состояние Background state распознается по значению свойства IsBackground, которое указывает на режим выполнения потока: background или foreground.
Любой Foreground -поток можно перевести в IsBackground в true.
Завершение Background -потока не влияет на завершение приложения в целом.
Завершение последнего Foreground -потока приводит к завершению приложения, независимо от состояния потоков, выполняемых в фоновом режиме.
Ниже в примере один из потоков переводится в
using System;
using System.Threading;
namespace ThreadApp_1
{
class Worker
{
int allTimes;
public Worker(int tKey)
{
allTimes = tKey;
}
// Тело рабочей функции...
public void DoItEasy()
{
int i;
for (i = 0; i < allTimes; i++)
{
Console.WriteLine("Back thread >>>> {0}\r",i);
}
Console.WriteLine("\nBackground thread was here!");
}
}
class StartClass
{
static void Main(string[] args)
{
long i;
Worker w0 = new Worker(100000);
ThreadStart t0;
t0 = new ThreadStart(w0.DoItEasy);
Thread th0;
th0 = new Thread(t0);
th0.IsBackground = true;
th0.Start();
for (i = 0; i < 100 ; i++)
{
Console.WriteLine("Fore thread: {0}", i);
}
Console.WriteLine("Foreground thread ended");
}
}
}
Задается значениями перечисления ThreadPriority. Эти значения используются при планировке очередности выполнения потоков в ПРОЦЕССЕ.
Каждый поток имеет собственный приоритет. Изначально он задается как Normal priority.
Алгоритм планировки выполнения потока позволяет системе определить последовательность выполнения потоков. Операционная система может также корректировать foreground в background.
Значение приоритета не влияет на состояние потока. Система планирует последовательность выполнения потоков на основе информации о состоянии потока.
Потоки с низшим уровнем приоритета выполняются лишь после того, как в процессе будет завершено выполнение потоков с более высоким приоритетом.
Highest |
AboveNormal |
Normal |
BelowNormal |
Lowest |
Делегат – представитель класса-делегата ThreadStart обеспечивает запуск вторичных потоков. Это элемент СТАНДАРТНОГО механизма поддержки вторичных потоков. Именно этим и объясняется главная особенность этого делегата: настраиваемые с его помощью стартовые функции потоков НЕ имеют параметров и не возвращают значений. Это означает, что невозможно осуществить запуск потока с помощью метода, имеющего параметры, а также получить какое-либо значение при завершении стартовой функции потока.
Ну и ладно! Все равно возвращаемое значение стартовой функции при существующем механизме запуска потока (функция Start ) некому перехватывать, а стандартный жесткий механизм предопределенных параметров (как у функции Main ) ничуть не лучше его полного отсутствия.
Если, конечно, существуют простые средства передачи данных в поток. Так вот, такие средства существуют!
Дело в том, что вторичный поток строится на основе методов конкретного класса. Это означает, что сначала создается объект – представитель класса, затем объект потока с настроенным на стартовую функцию делегатом, после чего поток стандартным образом запускается.
При этих условиях задача передачи данных потоку может быть возложена на конструкторы класса. На списки их параметров никаких особых ограничений не накладывается. В конструкторе могут быть реализованы самые сложные алгоритмы подготовки данных. Таким образом, "место для битвы" может быть подготовлено задолго до начала выполнения потока:
using System;
using System.Threading;
// Класс WorkThread содержит всю необходимую для выполнения
// данной задачи информацию, а также и соответствующий метод.
public class WorkThread
{
// State information used in the task.
private string entryInformation;
private int value;
// Конструктор получает всю необходимую информацию через параметры.
public WorkThread(string text, int number)
{
entryInformation = text;
value = number;
}
// Рабочий метод потока непосредственно после своего запуска
// получает доступ ко всей необходимой ранее подготовленной информации.
public void ThreadProc()
{
Console.WriteLine(entryInformation, value);
}
}
// Точка входа приложения.
//
public class Example
{
public static void Main()
{
// Подготовка к запуску вторичного потока предполагает создание
// объекта класса потока. В этот момент вся необходимая для работы потока
// информация передается через параметры конструктора.
// Здесь переданы необходимые детали, которые будут составлены
// стандартным образом в строку методом WriteLine.
WorkThread tws = new WorkThread("This report displays the number {0}.", 125);
// Создали объект потока, затем его запустили.
Thread t = new Thread(new ThreadStart(tws.ThreadProc));
t.Start();
Console.WriteLine("Первичный поток поработал. Теперь ждет.");
t.Join();
Console.WriteLine
("Сообщение из первичного потока: Вторичный поток отработал.");
Console.WriteLine
("Сообщение из первичного потока: Главный поток остановлен. ");
}
}
Первичный поток создал и запустил вторичный поток для решения определенной задачи. Вторичный поток выполнил поставленную задачу и... завершил свою работу. Возможно, что от результатов работы вторичного потока зависит дальнейшая работа приложения. Возможно, что до завершения выполнения вторичного потока первичному потоку вообще нечего делать и он приостановлен в результате выполнения метода Join.
Проблема заключается в том, КОГДА и КАКИМ ОБРАЗОМ о проделанной работе станет известно в первичном потоке.
Для анализа результата выполнения вторичного потока можно использовать метод класса, который обеспечивает запуск вторичного потока. Соответствующим образом настроенный делегат также может быть передан в качестве параметра конструктору вторичного потока. Вызывать метод класса, запустившего вторичный поток, можно будет по выполнении работ во ВТОРИЧНОМ потоке.
Таким образом, функция, контролирующая завершение работы вторичного потока, сама будет выполняться во ВТОРИЧНОМ потоке! Если при этом для дальнейшей работы первичного потока (который в этот момент, возможно, находится в состоянии ожидания) необходима информация о результатах проделанной вторичным потоком работы, контролирующая функция справится с этой задачей за счет того, что она имеет доступ ко всем данным и методам своего класса. И неважно, в каком потоке она при этом выполнялась:
using System;
using System.Threading;
// Класс WorkThread включает необходимую информацию,
// метод и делегат для вызова метода, который запускается
// после выполнения задачи.
public class WorkThread
{
// Входная информация.
private string entryInformation;
private int value;
// Ссылка на объект - представитель класса-делегата, с помощью которого
// вызывается метод обратного вызова. Сам класс-делегат объявляется позже.
private CallbackMethod callback;
// Конструктор получает входную информацию и настраивает
// callback delegate.
public WorkThread(string text, int number,
CallbackMethod callbackDelegate)
{
entryInformation = text;
value = number;
callback = callbackDelegate;
}
// Метод, обеспечивающий выполнение поставленной задачи:
// составляет строку и после дополнительной проверки настройки
// callback-делегата обеспечивает вызов метода.
public void ThreadProc()
{
Console.WriteLine(entryInformation, value);
if (callback != null)
callback(1); // Вот, вызвал ЧУЖОЙ МЕТОД в СВОЕМ потоке.
}
}
// Класс-делегат задает сигнатуру callback-методу.
//
public delegate void CallbackMethod(int lineCount);
// Entry Point for the example.
//
public class Example
{
public static void Main()
{
// Supply the state information required by the task.
WorkThread tws = new WorkThread("This report displays the number {0}.",
125,
new CallbackMethod(ResultCallback));
Thread t = new Thread(new ThreadStart(tws.ThreadProc));
t.Start();
Console.WriteLine("Первичный поток поработал. Теперь ждет.");
t.Join();
Console.WriteLine("Вторичный поток отработал. Главный поток остановлен.");
}
// Callback-метод, естественно, соответствует сигнатуре
// callback класса делегата.
public static void ResultCallback(int lineCount)
{
Console.WriteLine("Вторичный поток обработал {0} строк.", lineCount);
}
}
Callback-метод – метод – член класса, запустившего вторичный поток. Этот метод запускается "в качестве уведомления" о том, что вторичный поток "завершил выполнение своей миссии". Особенность Callback-метода заключается в том, что он выполняется в "чужом" потоке.
В предлагаемом примере вторичные потоки взаимодействуют между собой через общедоступный объект, располагаемый в общей памяти потоков. Данные для обработки подготавливаются соответствующими конструкторами классов, отвечающих за выполнение вторичных потоков. Конструктор класса, выполняемого во вторичном потоке, также отвечает за организацию уведомления главного потока о результатах взаимосвязанной деятельности вторичных потоков:
using System;
using System.Threading;
namespace CommunicatingThreadsData
{
public delegate void CallBackFromStartClass (long param);
// Данные. Предмет и основа взаимодействия двух потоков.
class CommonData
{
public long lVal;
public CommonData(long key)
{
lVal = key;
}
}
// Классы Worker и Inspector: основа взаимодействующих потоков.
class Worker
{
CommonData cd;
// Конструктор...
public Worker(ref CommonData rCDKey)
{
cd = rCDKey;
}
public void startWorker()
{
DoIt(ref cd);
}
// Тело рабочей функции...
public void DoIt(ref CommonData cData)
{//====================================
for (;;)
{
cData.lVal++; // Изменили значение...
Console.Write("{0,25}\r",cData.lVal); // Сообщили о результатах.
}
}//====================================
}
class Inspector
{
long stopVal;
CommonData cd;
CallBackFromStartClass callBack;
// Конструктор... Подготовка делегата для запуска CallBack-метода.
public Inspector
(ref CommonData rCDKey, long key, CallBackFromStartClass cbKey)
{
stopVal = key;
cd = rCDKey;
callBack = cbKey;
}
public void startInspector()
{
measureIt(ref cd);
}
// Тело рабочей функции...
public void measureIt(ref CommonData cData)
{//====================================
for (;;)
{
if (cData.lVal < stopVal)
{
Thread.Sleep(100);
Console.WriteLine("\n{0,–25}",cData.lVal);
}
else
callBack(cData.lVal);
}
}//====================================
}
class StartClass
{
static Thread th0, th1;
static CommonData cd;
static long result = 0;
static void Main(string[] args)
{
StartClass.cd = new CommonData(0);
// Конструкторы классов Worker и Inspector несут дополнительную нагрузку.
// Они обеспечивают необходимыми значениями методы,
// выполняемые во вторичных потоках.
Worker work;
// До начала выполнения потока вся необходимая информация доступна методу.
work = new Worker(ref cd);
Inspector insp;
// На инспектора возложена дополнительная обязанность
// вызова функции-терминатора.
// Для этого используется специально определяемый и настраиваемый делегат.
insp = new Inspector(ref cd,
50000,
new CallBackFromStartClass(StartClass.StopMain));
// Стартовые функции потоков должны соответствовать сигнатуре
// класса делегата ThreadStart. Поэтому они не имеют параметров.
ThreadStart t0, t1;
t0 = new ThreadStart(work.startWorker);
t1 = new ThreadStart(insp.startInspector);
// Созданы вторичные потоки.
StartClass.th0 = new Thread(t0);
StartClass.th1 = new Thread(t1);
// Запущены вторичные потоки.
StartClass.th0.Start();
StartClass.th1.Start();
// Еще раз о методе Join(): Выполнение главного потока приостановлено.
StartClass.th0.Join();
StartClass.th1.Join();
// Потому последнее слово остается за главным потоком приложения.
Console.WriteLine("Main(): All stoped at {0}. Bye.", result);
}
// Функция - член класса StartClass выполняется во ВТОРИЧНОМ потоке!
public static void StopMain(long key)
{
Console.WriteLine("StopMain: All stoped at {0}...", key);
// Остановка рабочих потоков. Ее выполняет функция - член
// класса StartClass. Этой функции в силу своего определения
// известно ВСЕ о вторичных потоках. Но выполняется она
// в ЧУЖОМ (вторичном) потоке. Поэтому:
// 1. надо предпринять особые дополнительные усилия для того, чтобы
// результат работы потоков оказался доступен в главном потоке.
/*StartClass.*/result = key;
// 2. очень важна последовательность остановки потоков,
StartClass.th0.Abort();
StartClass.th1.Abort();
// Этот оператор не выполняется! Поток, в котором выполняется
// метод - член класса StartClass StopMain(), остановлен.
Console.WriteLine("StopMain(): bye.");
}
}
}
Следующий вариант организации взаимодействия между потоками основан на использовании общедоступных свойств. От предыдущего примера отличается тем, что доступ к закрытому счетчику lVal в соответствии с принципами инкапсуляции осуществляется через свойство с блоками get (акцессор) и set (мутатор):
using System;
using System.Threading;
namespace CommunicatingThreadsData
{
public delegate void CallBackFromStartClass (long param);
// Данные. Предмет и основа взаимодействия двух потоков.
class CommonData
{
private long lVal;
public long lValProp
{
get
{
return lVal;
}
set
{
lVal = value;
}
}
public CommonData(long key)
{
lVal = key;
}
}
// Классы Worker и Inspector: основа взаимодействующих потоков.
class Worker
{
CommonData cd;
// Конструктор умолчания...
public Worker(ref CommonData rCDKey)
{
cd = rCDKey;
}
public void startWorker()
{
DoIt(ref cd);
}
// Тело рабочей функции...
public void DoIt(ref CommonData cData)
{//====================================
for (;;)
{
cData.lValProp++;
Console.Write("{0,25}\r",cData.lValProp);
}
}//====================================
}
class Inspector
{
long stopVal;
CommonData cd;
CallBackFromStartClass callBack;
// Конструктор...
public
Inspector(ref CommonData rCDKey, long key, CallBackFromStartClass cbKey)
{
stopVal = key;
cd = rCDKey;
callBack = cbKey;
}
public void startInspector()
{
measureIt(ref cd);
}
// Тело рабочей функции...
public void measureIt(ref CommonData cData)
{//====================================
for (;;)
{
if (cData.lValProp < stopVal)
{
Thread.Sleep(100);
Console.WriteLine("\n{0,–25}",cData.lValProp);
}
else
callBack(cData.lValProp);
}
}//====================================
}
class StartClass
{
static Thread th0, th1;
static CommonData cd;
static long result = 0;
static void Main(string[] args)
{
StartClass.cd = new CommonData(0);
// Конструкторы классов Worker и Inspector несут дополнительную нагрузку.
// Они обеспечивают необходимыми значениями методы,
// выполняемые во вторичных потоках.
Worker work;
// До начала выполнения потока вся необходимая информация доступна методу.
work = new Worker(ref cd);
Inspector insp;
// На инспектора возложена дополнительная обязанность
// вызова функции-терминатора.
// Для этого используется специально определяемый и настраиваемый
// делегат.
insp = new Inspector
(ref cd, 50000, new CallBackFromStartClass(StartClass.StopMain));
// Стартовые функции потоков должны соответствовать сигнатуре
// класса делегата ThreadStart. Поэтому они не имеют параметров.
ThreadStart t0, t1;
t0 = new ThreadStart(work.startWorker);
t1 = new ThreadStart(insp.startInspector);
// Созданы вторичные потоки.
StartClass.th0 = new Thread(t0);
StartClass.th1 = new Thread(t1);
// Запущены вторичные потоки.
StartClass.th0.Start();
StartClass.th1.Start();
// Еще раз о методе Join(): Выполнение главного потока приостановлено.
StartClass.th0.Join();
StartClass.th1.Join();
// Потому последнее слово остается за главным потоком приложения.
Console.WriteLine("Main(): All stoped at {0}. Bye.", result);
}
// Функция - член класса StartClass выполняется во ВТОРИЧНОМ потоке!
public static void StopMain(long key)
{
Console.WriteLine("StopMain: All stoped at {0}...", key);
// Остановка рабочих потоков. Ее выполняет функция - член
// класса StartClass. Этой функции в силу своего определения
// известно ВСЕ о вторичных потоках. Но выполняется она
// в ЧУЖОМ (вторичном) потоке. Поэтому:
// 1. надо предпринять особые дополнительные усилия для того, чтобы
// результат работы потоков оказался доступен в главном потоке.
/*StartClass.*/result = key;
// 2. очень важна последовательность остановки потоков,
StartClass.th0.Abort();
StartClass.th1.Abort();
// Этот оператор не выполняется! Поток, в котором выполняется
// метод - член класса StartClass StopMain(), остановлен.
Console.WriteLine("StopMain(): bye.");
}
}
}
Queue – класс, который представляет коллекцию объектов ( objects ), работающую по принципу "первым пришел, первым ушел" (first in, first out).
Stack – класс, который представляет коллекцию объектов ( objects ), работающую по принципу "последним пришел, первым ушел" (last in, first out).
Взаимодействующие потоки выполняют поставленную перед ними задачу. При этом один поток обеспечивает генерацию данных, а второй – обработку получаемых данных. Если при этом время, необходимое для генерации данных, и время обработки данных различаются, проблема взаимодействия потоков может быть решена посредством очереди данных, которая в этом случае играет роль буфера между двумя потоками. Первый поток размещает данные в очередь "с одного конца", абсолютно не интересуясь успехами потока-обработчика. Второй поток извлекает данные из очереди "с другого конца", руководствуясь исключительно состоянием этой очереди. Отсутствие данных в очереди для потока-обработчика означает успешное выполнение поставленной задачи или сигнал к самоусыплению.
Организация работы потоков по этой схеме предполагает:
Пример:
using System;
using System.Threading;
using System.Collections;
namespace CommunicatingThreadsQueue
{
public delegate void CallBackFromStartClass (string param);
// Данные. Предмет и основа взаимодействия двух потоков.
class CommonData
{
private int iVal;
public int iValProp
{
get
{
return iVal;
}
set
{
iVal = value;
}
}
public CommonData(int key)
{
iVal = key;
}
}
// Классы Receiver и Sender: основа взаимодействующих потоков.
class Receiver
{
Queue cdQueue;
CallBackFromStartClass callBack;
// Конструктор умолчания...
public Receiver(ref Queue queueKey, CallBackFromStartClass cbKey)
{
cdQueue = queueKey;
callBack = cbKey;
}
public void startReceiver()
{
DoIt();
}
// Тело рабочей функции...
public void DoIt()
{
CommonData cd = null;
while (true)
{
Console.WriteLine("Receiver. notifications in queue: {0}",cdQueue.Count);
if (cdQueue.Count > 0)
{
cd = (CommonData)cdQueue.Dequeue();
if (cd == null)
Console.WriteLine("?????");
else
{
Console.WriteLine("Process started ({0}).", cd.iValProp);
// Выбрать какой-нибудь из способов обработки полученного уведомления.
// Заснуть на соответствующее количество тиков.
// Thread.Sleep(cd.iValProp);
// Заняться элементарной арифметикой. С усыплением потока.
while (cd.iValProp != 0)
{
cd.iValProp––;
Thread.Sleep(cd.iValProp);
Console.WriteLine("process:{0}",cd.iValProp);
}
}
}
else
callBack("Receiver");
Thread.Sleep(100);
}
}
}
class Sender
{
Random rnd;
int stopVal;
Queue cdQueue;
CallBackFromStartClass callBack;
// Конструктор...
public Sender(ref Queue queueKey, int key, CallBackFromStartClass cbKey)
{
rnd = new Random(key);
stopVal = key;
cdQueue = queueKey;
callBack = cbKey;
}
public void startSender()
{
sendIt();
}
// Тело рабочей функции...
public void sendIt()
{//====================================
while (true)
{
if (stopVal > 0)
{
// Размещение в очереди нового члена со случайными характеристиками.
cdQueue.Enqueue(new CommonData(rnd.Next(0,stopVal)));
stopVal––;
}
else
callBack("Sender");
Console.WriteLine("Sender. in queue:{0}, the rest of notifications:{1}.",
cdQueue.Count, stopVal);
Thread.Sleep(100);
}
}
}
class StartClass
{
static Thread th0, th1;
static Queue NotificationQueue;
static string[] report = new string[2];
static void Main(string[] args)
{
StartClass.NotificationQueue = new Queue();
// Конструкторы классов Receiver и Sender несут дополнительную нагрузку.
// Они обеспечивают необходимыми значениями методы,
// выполняемые во вторичных потоках.
Sender sender;
// По окончании работы отправитель вызывает функцию-терминатор.
// Для этого используется специально определяемый и настраиваемый
// делегат.
sender = new Sender(ref NotificationQueue,
100,
new CallBackFromStartClass(StartClass.StopMain));
Receiver receiver;
// Выбрав всю очередь, получатель вызывает функцию-терминатор.
receiver = new Receiver(ref NotificationQueue,
new CallBackFromStartClass(StartClass.StopMain));
// Стартовые функции потоков должны соответствовать сигнатуре
// класса делегата ThreadStart. Поэтому они не имеют параметров.
ThreadStart t0, t1;
t0 = new ThreadStart(sender.startSender);
t1 = new ThreadStart(receiver.startReceiver);
// Созданы вторичные потоки.
StartClass.th0 = new Thread(t0);
StartClass.th1 = new Thread(t1);
// Запущены вторичные потоки.
StartClass.th0.Start();
StartClass.th1.Start();
// Еще раз о методе Join():
// Выполнение главного потока приостановлено до завершения
// выполнения вторичных потоков.
StartClass.th0.Join();
StartClass.th1.Join();
// Потому последнее слово остается за главным потоком приложения.
Console.WriteLine("Main(): " + report[0] + "..." + report[1] + "... Bye.");
}
// Функция - член класса StartClass выполняется во ВТОРИЧНОМ потоке!
public static void StopMain(string param)
{
Console.WriteLine("StopMain: " + param);
// Остановка рабочих потоков. Ее выполняет функция - член
// класса StartClass. Этой функции в силу своего определения
// известно ВСЕ о вторичных потоках. Но выполняется она
// в ЧУЖИХ (вторичных) потоках.
if (param.Equals("Sender"))
{
report[0] = "Sender all did.";
StartClass.th0.Abort();
}
if (param.Equals("Receiver"))
{
report[1] = "Receiver all did.";
StartClass.th1.Abort();
}
// Этот оператор не выполняется! Поток, в котором выполняется
// метод - член класса StartClass StopMain(), остановлен.
Console.WriteLine("StopMain(): bye.");
}
}
}
В ранее рассмотренном примере временные задержки при генерации и обработке данных подобраны таким образом, что обработчик завершает свою деятельность последним. Таким образом, обеспечивается обработка ВСЕГО множества данных, размещенных в очереди. Разумеется, это идеальная ситуация. Изменение соответствующих значений может привести к тому, что обработчик данных опустошит очередь и завершит работу раньше, чем генератор данных разместит в очереди все данные.
Таким образом, результаты выполнения программы оказываются зависимыми от обстоятельств, никаким образом не связанных с поставленной задачей.
Подобная ситуация хорошо известна как "
Блокировка выполнения потока возникает при совместном использовании потоками нескольких ресурсов. В условиях, когда выполнение потоков явным образом не управляется, поток в нужный момент может не получить доступа к требуемому ресурсу, поскольку именно сейчас этот ресурс используется другим потоком.
Тупик – взаимная блокировка потоков:
поток A захватывает ресурс a и не может получить доступа к ресурсу b, занятому потоком B, который может быть освобожден потоком только по получению доступа к ресурсу a.
Пример взаимодействующих потоков рассматривается далее:
// Взаимодействующие потоки разделяют общие ресурсы – пару очередей.
// Для успешной работы каждый поток должен последовательно получить
// доступ к каждой из очередей. Из одной очереди взять, в другую
// положить. Поток оказывается заблокирован, когда одна из очередей
// оказывается занятой другим потоком.
using System;
using System.Threading;
using System.Collections;
namespace CommunicatingThreadsQueue
{
// Модифифицированный вариант очереди – очередь с флажком.
// Захвативший эту очередь поток объявляет очередь "закрытой".
public class myQueue: Queue
{
private bool isFree;
public bool IsFree
{
get
{
return isFree;
}
set
{
isFree = value;
}
}
public object myDequeue()
{
if (IsFree) {IsFree = false; return base.Dequeue();}
else return null;
}
public bool myEnqueue(object obj)
{
if (IsFree == true) {base.Enqueue(obj); return true;}
else return false;
}
}
public delegate void CallBackFromStartClass (string param);
// Данные. Предмет и основа взаимодействия двух потоков.
class CommonData
{
private int iVal;
public int iValProp
{
get
{
return iVal;
}
set
{
iVal = value;
}
}
public CommonData(int key)
{
iVal = key;
}
}
// Классы Receiver и Sender: основа взаимодействующих потоков.
class Receiver
{
myQueue cdQueue0;
myQueue cdQueue1;
CallBackFromStartClass callBack;
int threadIndex;
// Конструктор...
public Receiver(ref myQueue queueKey0,
ref myQueue queueKey1,
CallBackFromStartClass cbKey,
int iKey)
{
threadIndex = iKey;
if (threadIndex == 0)
{
cdQueue0 = queueKey0;
cdQueue1 = queueKey1;
}
else
{
cdQueue1 = queueKey0;
cdQueue0 = queueKey1;
}
callBack = cbKey;
}
public void startReceiver()
{
DoIt();
}
// Тело рабочей функции...
public void DoIt()
{
CommonData cd = null;
while (true)
{
if (cdQueue0.Count > 0)
{
while (true)
{
cd = (CommonData)cdQueue0.myDequeue();
if (cd != null) break;
Console.WriteLine(">> Receiver{0} is blocked.", threadIndex);
}
// Временная задержка "на обработку" полученного блока информации
// влияет на частоту и продолжительность блокировок.
Thread.Sleep(cd.iValProp*100);
// И это не ВЗАИМНАЯ блокировка потоков.
// "Обработали" блок – открыли очередь.
// И только потом предпринимается попытка
// обращения к очереди оппонента.
cdQueue0.IsFree = true;
//Записали результат во вторую очередь.
while (cdQueue1.myEnqueue(cd) == false)
{
Console.WriteLine("<< Receiver{0} is blocked.", threadIndex);
}
// А вот если пытаться освободить захваченную потоком очередь
// в этом месте – взаимной блокировки потоков не избежать!
// cdQueue0.IsFree = true;
// Сообщили о состоянии очередей.
Console.WriteLine("Receiver{0}...{1}>{2}",threadIndex.ToString(),
cdQueue0.Count,cdQueue1.Count);
}
else
{
cdQueue0.IsFree = true;
callBack(string.Format("Receiver{0}",threadIndex.ToString()));
}
}
}
}
class Sender
{
Random rnd;
int stopVal;
myQueue cdQueue0;
myQueue cdQueue1;
CallBackFromStartClass callBack;
// Конструктор...
public Sender(ref myQueue queueKey0,
ref myQueue queueKey1,
int key,
CallBackFromStartClass cbKey)
{
rnd = new Random(key);
stopVal = key;
cdQueue0 = queueKey0;
cdQueue1 = queueKey1;
callBack = cbKey;
}
public void startSender()
{
sendIt();
}
// Тело рабочей функции...
public void sendIt()
{//====================================
cdQueue0.IsFree = false;
cdQueue1.IsFree = false;
while (true)
{
if (stopVal > 0)
{
// Размещение в очереди нового члена со случайными характеристиками.
cdQueue0.Enqueue(new CommonData(rnd.Next(0,stopVal)));
cdQueue1.Enqueue(new CommonData(rnd.Next(0,stopVal)));
stopVal––;
}
else
{
cdQueue0.IsFree = true;
cdQueue1.IsFree = true;
callBack("Sender");
}
Console.WriteLine
("Sender. The rest of notifications: {0}, notifications in queue:{1},{2}.",
stopVal, cdQueue0.Count, cdQueue1.Count);
}
}//====================================
}
class StartClass
{
static Thread th0, th1, th2;
static myQueue NotificationQueue0;
static myQueue NotificationQueue1;
static string[] report = new string[3];
static void Main(string[] args)
{
StartClass.NotificationQueue0 = new myQueue();
StartClass.NotificationQueue1 = new myQueue();
// Конструкторы классов Receiver и Sender несут дополнительную нагрузку.
// Они обеспечивают необходимыми значениями методы,
// выполняемые во вторичных потоках.
Sender sender;
// По окончании работы отправитель вызывает функцию-терминатор.
// Для этого используется специально определяемый и настраиваемый делегат.
sender = new Sender(ref NotificationQueue0,
ref NotificationQueue1,
10, new CallBackFromStartClass(StartClass.StopMain));
Receiver receiver0;
// Выбрав всю очередь, получатель вызывает функцию-терминатор.
receiver0 = new Receiver(ref NotificationQueue0,
ref NotificationQueue1,
new CallBackFromStartClass(StartClass.StopMain),0);
Receiver receiver1;
// Выбрав всю очередь, получатель вызывает функцию-терминатор.
receiver1 = new Receiver(ref NotificationQueue0,
ref NotificationQueue1,
new CallBackFromStartClass(StartClass.StopMain),1);
// Стартовые функции потоков должны соответствовать сигнатуре
// класса делегата ThreadStart. Поэтому они не имеют параметров.
ThreadStart t0, t1, t2;
t0 = new ThreadStart(sender.startSender);
t1 = new ThreadStart(receiver0.startReceiver);
t2 = new ThreadStart(receiver1.startReceiver);
// Созданы вторичные потоки.
StartClass.th0 = new Thread(t0);
StartClass.th1 = new Thread(t1);
StartClass.th2 = new Thread(t2);
// Запущены вторичные потоки.
StartClass.th0.Start();
// Еще раз о методе Join():
// Выполнение главного потока приостановлено до завершения
// выполнения вторичного потока загрузки очередей.
// Потоки получателей пока отдыхают.
StartClass.th0.Join();
// Отработал поток загрузчика.
// Очередь получателей.
StartClass.th1.Start();
StartClass.th2.Start();
// Метод Join():
// Выполнение главного потока опять приостановлено
// до завершения выполнения вторичных потоков.
StartClass.th1.Join();
StartClass.th2.Join();
// Последнее слово остается за главным потоком приложения.
// Но только после того, как отработают терминаторы.
Console.WriteLine
("Main():"+report[0]+". "+report[1]+". "+report[2]+". Bye.");
}
// Функция - член класса StartClass выполняется во ВТОРИЧНОМ потоке!
public static void StopMain(string param)
{
Console.WriteLine("StopMain: " + param);
// Остановка рабочих потоков. Ее выполняет функция - член
// класса StartClass. Этой функции в силу своего определения
// известно ВСЕ о вторичных потоках. Но выполняется она
// в ЧУЖИХ (вторичных) потоках.
if (param.Equals("Sender"))
{
report[0] = "Sender all did.";
StartClass.th0.Abort();
}
if (param.Equals("Receiver0"))
{
report[1] = "Receiver0 all did.";
StartClass.th1.Abort();
}
if (param.Equals("Receiver1"))
{
report[2] = "Receiver1 all did.";
StartClass.th2.Abort();
}
// Этот оператор не выполняется! Поток, в котором выполняется
// метод - член класса StartClass StopMain(), остановлен.
Console.WriteLine("StopMain(): bye.");
}
}
}
Некоторое значение, связанное с конкретным объектом, подвергается воздействию (изменению, преобразованию) со стороны потока. Это означает, что по отношению к объекту (значению объекта) применяется фиксированная последовательность операторов, в результате которой происходит КОРРЕКТНОЕ изменение состояния объекта или его значения.
В многопоточном приложении один и тот же объект может быть подвергнут одновременному "параллельному" воздействию со стороны нескольких потоков. Подобное воздействие представляет опасность для объекта и его значения, поскольку в этом случае порядок применения операторов из нескольких потоков (пусть даже и содержащих одни и те же операторы) неизбежно будет изменен.
В многопоточном программировании последовательности операторов, составляющих поток и при неконтролируемом доступе к объекту, возможно, приводящих к некорректному изменению состояния объекта, называются критическими секциями кода.
Управление последовательностью доступа потоков к объекту называют синхронизацией потоков.
Сам же объект называют объектом синхронизации.
Типичными средствами синхронизации потоков являются:
Очередь — достаточно сложное образование с множеством свойств и методов, предназначенное для упорядоченного размещения объектов (все дети класса object ). Одновременное воздействие на очередь со стороны кода нескольких потоков представляет серьезную опасность — и не столько для самого объекта очереди в смысле возможного искажения сохраняемой в ней информации, сколько для самого приложения. Класс Queue взаимодействует с окружением через интерфейсы, а неупорядоченное воздействие на объект очереди через эти интерфейсы возбуждает исключения. Очередь располагает специальными средствами, позволяющими защитить объект от неупорядоченного воздействия со стороны множества потоков. Назначение некоторых средств и их применение очевидно, как использовать другие средства – пока неясно (я пометил их вопросительным знаком).
В класс входят методы и свойства:
Queue(...) ;object ) – void Enqueue (object), object Dequeue () ;bool Contains(object) ;object Peek () (возвращает объект из начала очереди, не удаляя его из очереди);Count – сохраняет информацию о количестве объектов в очереди;SyncRoot – предоставляет ссылку на ОБЪЕКТ СИНХРОНИЗАЦИИ, который используется при синхронизации потоков многопоточного приложения;IsSynchronized – предоставляет информацию о том, синхронизирован ли объект для работы в многопоточном приложении. Это всего лишь значение объявленной в классе Queue булевской переменной;Synchronized – создающий синхронизированную оболочку вокруг объекта очереди.Примеры использования очередей в приложении приводятся ниже, а пока – вопросы, связанные с взаимодействием объекта очереди с потоками многопоточного приложения.
Перебор элементов очереди посредством оператора цикла foreach – самое "опасное" для очереди занятие в условиях многопоточного приложения. И причина всех неприятностей заключается во внутреннем устройстве и особенностях реализации цикла foreach, который при своем выполнении использует множество функций интерфейса очереди:
Queue myCollection = new Queue();
::::::::::::::::::::::::::::::::::
// Перебор элементов очереди – критическая секция кода.
foreach ( Object item in myCollection )
{
:::::::::::::::::::::::::::::::::::::::
}
Возможный способ преодоления
Queue myCollection = new Queue();
::::::::::::::::::::::::::::::::::::::
lock( myCollection.SyncRoot )
{// Критическая секция, которая связана с объектом
// синхронизации, полученным от очереди
// myCollection, обозначена...
foreach ( Object item in myCollection )
{
:::::::::::::::::::::::::::::::::::::::
}
}
Пример синхронизации объекта очереди. Видно, как создавать синхронизированную оболочку вокруг несинхронизированной очереди, как узнавать о том, синхронизирована она или нет, НО ЗАЧЕМ ДЕЛАТЬ ЭТО – не сказано и не показано. Дело в том, что синхронизирована она или нет, а соответствующий код (критические секции кода) защищать все равно надо!
using System;
using System.Collections;
public class SamplesQueue {
public static void Main() {
// Creates and initializes a new Queue.
Queue myQ = new Queue();
myQ.Enqueue( "The" );
myQ.Enqueue( "quick" );
myQ.Enqueue( "brown" );
myQ.Enqueue( "fox" );
// Creates a synchronized wrapper around the Queue.
Queue mySyncdQ = Queue.Synchronized( myQ );
// Displays the sychronization status of both Queues.
Console.WriteLine("myQ is {0}.",
myQ.IsSynchronized ? "synchronized" : "not synchronized" );
Console.WriteLine( "mySyncdQ is {0}.",
mySyncdQ.IsSynchronized ? "synchronized" : "not synchronized" );
}
}
//This code produces the following output.
//myQ is not synchronized.
//mySyncdQ is synchronized.
Stack – класс, который представляет коллекцию объектов, обслуживаемую по принципу "последним пришел — первым вышел".
Список всех членов этого типа представлен в разделе "Stack-члены".
public class Stack : ICollection, IEnumerable, ICloneable
Открытые статические (Shared в Visual Basic) члены этого типа могут использоваться для многопоточных операций. Потокобезопасность членов экземпляра не гарантируется.
Для обеспечения потокобезопасности операций с классом Stack, все они должны выполняться с помощью обертки, возвращенной методом Synchronized.
Перечисление в коллекции в действительности не является потокобезопасной процедурой. Даже при синхронизации коллекции другие потоки могут изменить ее, что приводит к созданию исключения при перечислении. Чтобы обеспечить потокобезопасность при перечислении, можно либо заблокировать коллекцию на все время перечисления, либо перехватывать исключения, которые возникают в результате изменений, внесенных другими потоками.
// Пара потоков "наперегонки" заполняет одну очередь.
// Эти потоки синхронизуются посредством критических секций кода,
// связанных с разделяемым ресурсом – общей очередью.
// Третий поток читает из этой очереди.
// Этот поток синхронизуется посредством монитора.
// Методы Enter(...) и Exit(...) обеспечивают вход в критическую секцию
// кода, связанную с конкретным разделяемым объектом, и тем самым
// блокируют одновременное выполнение какого-либо связанного
// с данным ресурсом кода в другом потоке.
// Толчея потоков сопровождается генерацией исключений.
using System;
using System.Threading;
using System.Collections;
namespace CommunicatingThreadsQueue
{
public delegate void CallBackFromStartClass (string param);
// Данные. Предмет и основа взаимодействия двух потоков.
class CommonData
{
private int iVal;
public int iValProp
{
get{return iVal;}
set{iVal = value;}
}
public CommonData(int key)
{
iVal = key;
}
}
// Классы Sender и Receiver: основа взаимодействующих потоков.
class Sender
{
Queue cdQueue;
CallBackFromStartClass callBack;
int threadIndex;
// Конструктор...
public Sender(ref Queue queueKey, CallBackFromStartClass cbKey, int iKey)
{
cdQueue = queueKey;
callBack = cbKey;
threadIndex = iKey;
}
public void startSender()
{
DoIt();
}
// Тело рабочей функции...
public void DoIt()
{//==============================================================
Console.WriteLine("Sender{0}.DoIt()", threadIndex);
int i;
for (i = 0; i < 100; i++)
{//==============================================================
try
{
lock(cdQueue.SyncRoot)
{//______________________________________________________________
Console.WriteLine("Sender{0}.", threadIndex);
cdQueue.Enqueue(new CommonData(i));
Console.WriteLine(">> Sender{0} >> {1}.", threadIndex,cdQueue.Count);
foreach(CommonData cd in cdQueue)
{
Console.Write("\rS{0}:{1} ", threadIndex, cd.iValProp);
}
Console.WriteLine("__ Sender{0} __", threadIndex);
}//______________________________________________________________
}
catch (ThreadAbortException e)
{
Console.WriteLine("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
Console.WriteLine("AbortException from Sender{0}.", threadIndex);
Console.WriteLine(e.ToString());
Console.WriteLine("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
catch (Exception e)
{
Console.WriteLine("___________________________________________");
Console.WriteLine("Exception from Sender{0}.", threadIndex);
Console.WriteLine(e.ToString());
Console.WriteLine("___________________________________________");
callBack(threadIndex.ToString());
}
}//=============================================================
callBack(string.Format("Sender{0}",threadIndex.ToString()));
}//==============================================================
}
class Receiver
{
Queue cdQueue;
CallBackFromStartClass callBack;
int threadIndex;
// Конструктор...
public Receiver(ref Queue queueKey, CallBackFromStartClass cbKey, int iKey)
{
cdQueue = queueKey;
callBack = cbKey;
threadIndex = iKey;
}
public void startReceiver()
{
DoIt();
}
// Тело рабочей функции...
public void DoIt()
{
Console.WriteLine("Receiver.DoIt()");
int i = 0;
CommonData cd;
while (i < 200)
{
try
{
Monitor.Enter(cdQueue.SyncRoot);
Console.WriteLine("Receiver.");
if (cdQueue.Count > 0)
{
cd = (CommonData)cdQueue.Dequeue();
Console.WriteLine
("Receiver.current:{0},in queue:{1}.",cd.iValProp,cdQueue.Count);
foreach(CommonData cdW in cdQueue)
{
Console.Write("\rR:{0}.", cdW.iValProp);
}
Console.WriteLine("__ Receiver __");
i++;
}
Monitor.Exit(cdQueue.SyncRoot);
}
catch (ThreadAbortException e)
{
Console.WriteLine("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
Console.WriteLine("AbortException from Receiver.");
Console.WriteLine(e.ToString());
Console.WriteLine("~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
}
catch (Exception e)
{
Console.WriteLine("___________________________________________");
Console.WriteLine("Exception from Receiver.");
Console.WriteLine(e.ToString());
Console.WriteLine("____________________________________________");
callBack(threadIndex.ToString());
}
}
callBack("Receiver");
}
}
class StartClass
{
Thread th0, th1, th2;
Queue queueX;
string[] report = new string[3];
ThreadStart t0, t1, t2;
Sender sender0;
Sender sender1;
Receiver receiver;
static void Main(string[] args)
{
StartClass sc = new StartClass();
sc.go();
}
void go()
{
// Простая очередь.
// queueX = new Queue();
// Синхронизированная очередь. Строится на основе простой очереди.
// Свойство синхронизированности дополнительно устанавливается в true
// посредством метода Synchronized.
queueX = Queue.Synchronized(new Queue());
// Но на самом деле никакой разницы между двумя версиями очереди
// (между несинхронизированной очередью и синхронизирванной оболочкой
// вокруг несинхронизированной очереди) мною замечено не было.
// И в том и в другом
// случае соответствующий код, который обеспечивает перебор
// элементов очереди, должен быть закрыт посредством lock-блока,
// с явным указанием ссылки на объект синхронизации.
sender0 = new Sender(ref queueX, new CallBackFromStartClass(StopMain), 0);
sender1 = new Sender(ref queueX, new CallBackFromStartClass(StopMain), 1);
receiver = new Receiver(ref queueX, new CallBackFromStartClass(StopMain), 2);
// Стартовые функции потоков должны соответствовать сигнатуре
// класса делегата ThreadStart. Поэтому они не имеют параметров.
t0 = new ThreadStart(sender0.startSender);
t1 = new ThreadStart(sender1.startSender);
t2 = new ThreadStart(receiver.startReceiver);
// Созданы вторичные потоки.
th0 = new Thread(t0);
th1 = new Thread(t1);
th2 = new Thread(t2);
th0.Start();
th1.Start();
th2.Start();
th0.Join();
th1.Join();
th2.Join();
Console.WriteLine
("Main(): " + report[0] + "..." + report[1] + "..." + report[2] + "... Bye.");
}
// Функция-член класса StartClass выполняется во ВТОРИЧНОМ потоке!
public void StopMain(string param)
{
Console.WriteLine("StopMain: " + param);
// Остановка рабочих потоков. Ее выполняет функция - член
// класса StartClass. Этой функции в силу своего определения
// известно ВСЕ о вторичных потоках. Но выполняется она
// в ЧУЖИХ (вторичных) потоках.
if (param.Equals("Sender0"))
{
report[0] = "Sender0 all did.";
th0.Abort();
}
if (param.Equals("Sender1"))
{
report[1] = "Sender1 all did.";
th1.Abort();
}
if (param.Equals("Receiver"))
{
report[2] = "Receiver all did.";
th2.Abort();
}
if (param.Equals("0"))
{
th1.Abort();
th2.Abort();
th0.Abort();
}
if (param.Equals("1"))
{
th0.Abort();
th2.Abort();
th1.Abort();
}
if (param.Equals("2"))
{
th0.Abort();
th1.Abort();
th2.Abort();
}
// Этот оператор не выполняется! Поток, в котором выполняется
// метод - член класса StartClass StopMain(), остановлен.
Console.WriteLine("StopMain(): bye.");
}
}
}
Класс Monitor управляет доступом к коду с использованием объекта синхронизации. Объект синхронизации предоставляет возможности для ограничения доступа к блоку кода, в общем случае обозначаемого как критическая секция.
Поток выполняет операторы. Выполнение оператора, обеспечивающего захват с помощью монитора объекта синхронизации, закрывает критическую секцию кода.
Другие потоки, выполняющие данную последовательность операторов, не могут продвинуться дальше оператора захвата монитором объекта синхронизации и переходят в состояние ожидания до тех пор, пока поток, захвативший с помощью монитора критическую секцию кода, не освободит ее.
Таким образом, монитор гарантирует, что никакой другой поток не сможет обратиться к коду, выполняемому потоком, который захватил с помощью монитора и данного объекта синхронизации критическую секцию кода, пока она не будет освобождена, если только потоки не выполняют данную секцию кода с использованием другого объекта синхронизации.
Следующая таблица описывает действия, которые могут быть предприняты потоками при взаимодействии с монитором:
| Действие | Описание |
|---|---|
Enter, TryEnter |
Закрытие секции с помощью объекта синхронизации. Это действие также обозначает начало критической секции. Никакие другие потоки не могут войти в заблокированную критическую секцию, если только они не используют другой объект синхронизации |
Exit |
Освобождает блокировку критической секции кода. Также обозначает конец критической секции, связанной с данным объектом синхронизации |
Wait |
Поток переходит в состояние ожидания, предоставляя тем самым другим потокам возможность выполнения других критических секций кода, связанных с данным объектом синхронизации. В состоянии ожидания поток остается до тех пор, пока на выходе из другой секции, связанной с данным объектом синхронизации, другой поток не выполнит на мониторе действия |
|
Посылает сигнал ожидающим потокам. Сигнал служит уведомлением ожидающему потоку, что состояние объекта синхронизации изменилось и что |
Enter - и Exit -методы используются для обозначения начала или конца критической секции. Если критическая секция представляет собой "непрерывное" множество инструкций, закрытие кода посредством метода Enter гарантирует, что только один поток сможет выполнять код, закрытый объектом синхронизации.
Рекомендуется размещать эти инструкции в try block и помещать Exit instruction в finally -блоке.
Все эти возможности обычно используются для синхронизации доступа к статическим и нестатическим методам класса. Нестатический метод блокируется посредством объекта синхронизации.
// Синхронизация потоков с использованием класса монитора.
// Монитор защищает очередь от параллельного вторжения со стороны
// взаимодействующих потоков из разных фрагментов кода.
// Однако монитор не может защитить потоки от взаимной блокировки.
// Поток просыпается, делает свою работу, будит конкурента, засыпает сам.
// К тому моменту, как поток будит конкурента, конкурент должен спать.
// Активизация незаснувшего потока не имеет никаких последствий.
// Если работающий поток разбудит не успевший заснуть поток – возникает
// тупиковая ситуация. Оба потока оказываются погруженными в сон.
// В этом случае имеет смысл использовать перегруженный вариант метода
// Wait – с указанием временного интервала.
using System;
using System.Threading;
using System.Collections;
namespace MonitorCS1
{
class MonitorApplication
{
const int MAX_LOOP_TIME = 100;
Queue xQueue;
public MonitorApplication()
{
xQueue = new Queue();
}
public void FirstThread()
{
int counter = 0;
while(counter < MAX_LOOP_TIME)
{
Console.WriteLine("Thread_1___");
counter++;
Console.WriteLine("Thread_1...{0}", counter);
try
{
//Push element.
xQueue.Enqueue(counter);
foreach(int ctr in xQueue)
{
Console.WriteLine(":::Thread_1:::{0}", ctr);
}
}
catch (Exception ex)
{
Console.WriteLine(ex.ToString());
}
//Release the waiting thread. Применяется к конкурирующему потоку.
lock(xQueue){Monitor.Pulse(xQueue);}
Console.WriteLine(">1 Wait<");
//Wait, if the queue is busy. Применяется к текущему потоку.
// Собственное погружение в состояние ожидания.
lock(xQueue){Monitor.Wait(xQueue,1000);}
Console.WriteLine("!1 Work!");
}
Console.WriteLine("*****1 Finish*****");
lock(xQueue) {Monitor.Pulse(xQueue);}
}
public void SecondThread()
{
int counter = 0;
while(counter < MAX_LOOP_TIME)
{
//Release the waiting thread. Применяется к конкурирующему потоку.
lock(xQueue){Monitor.Pulse(xQueue);}
Console.WriteLine(">2 Wait<");
// Собственное погружение в состояние ожидания.
lock(xQueue){Monitor.Wait(xQueue,1000);}
Console.WriteLine("!2 Work!");
Console.WriteLine("Thread_2___");
try
{
foreach(int ctr in xQueue)
{
Console.WriteLine(":::Thread_2:::{0}", ctr);
}
//Pop element.
counter = (int)xQueue.Dequeue();
}
catch (Exception ex)
{
counter = MAX_LOOP_TIME;
Console.WriteLine(ex.ToString());
}
Console.WriteLine("Thread_2...{0}",counter);
}
Console.WriteLine("*****2 Finish*****");
lock(xQueue) {Monitor.Pulse(xQueue);}
}
static void Main(string[] args)
{
// Create the MonitorApplication object.
MonitorApplication test = new MonitorApplication();
Thread tFirst = new Thread(new ThreadStart(test.FirstThread));
// Вторичные потоки созданы!
Thread tSecond = new Thread(new ThreadStart(test.SecondThread));
//Start threads.
tFirst.Start();
tSecond.Start();
// Ждать завершения выполнения вторичных потоков.
tFirst.Join();
tSecond.Join();
}
}
}
Когда двум или более потокам нужно произвести доступ к разделяемому ресурсу одновременно, системе необходим механизм синхронизации для того, чтобы гарантировать использование ресурса только одним процессом. Класс — это примитив синхронизации, который предоставляет эксклюзивный доступ к разделяемому ресурсу только для одного процесса. Если поток получает семафор, второй поток, желающий получить этот семафор, приостанавливается до тех пор, пока первый поток не освободит семафор.
Можно использовать метод WaitHandle.WaitOne для запроса на владение семафором. Поток, владеющий семафором, может запрашивать его в повторяющихся вызовах Wait без блокировки выполнения. Однако поток должен вызвать метод ReleaseMutex соответственное количество раз для того, чтобы прекратить владеть семафором. Если поток завершается нормально во время владения семафором, состояние семафора задается сигнальным, и следующий ожидающий поток становится владельцем семафора. Если нет потоков, владеющих семафором, его состояние является сигнальным.
- конструктор |
Перегружен. Инициализирует новый экземпляр класса |
Handle (унаследовано от WaitHandle ) |
Получает или задает собственный дескриптор операционной системы |
Close (унаследовано от WaitHandle ) |
При переопределении в производном классе освобождает все ресурсы, занимаемые текущим объектом WaitHandle |
CreateObjRef (унаследовано от MarshalByRefObject ) |
Создает объект, который содержит всю необходимую информацию для разработки прокси-сервера, используемого для коммуникации с удаленными объектами |
Equals (унаследовано от Object ) |
Перегружен. Определяет, равны ли два экземпляра Object |
GetHashCode (унаследовано от Object ) |
Служит хэш-функцией для конкретного типа, пригоден для использования в |
GetLifetimeService (унаследовано от MarshalByRefObject ) |
Извлекает служебный объект текущего срока действия, который управляет средствами срока действия данного экземпляра |
GetType (унаследовано от Object ) |
Возвращает Type текущего экземпляра |
InitializeLifetimeService (унаследовано от MarshalByRefObject ) |
Получает служебный объект срока действия для управления средствами срока действия данного экземпляра |
ReleaseMutex |
Освобождает объект один раз |
ToString (унаследовано от Object ) |
Возвращает String, который представляет текущий Object |
WaitOne (унаследовано от WaitHandle ) |
Перегружен. В случае переопределения в производном классе, блокирует текущий поток до получения сигнала текущим объектом WaitHandle |
Dispose (унаследовано от WaitHandle ) |
При переопределении в производном классе отключает неуправляемые ресурсы, используемые WaitHandle, и по возможности освобождает управляемые ресурсы |
Finalize (унаследовано от WaitHandle ) |
Переопределен. Освобождает ресурсы, удерживаемые текущим экземпляром. В языках C# и C++ для функций финализации используется синтаксис деструктора |
MemberwiseClone (унаследовано от Object ) |
Создает неполную копию текущего объекта object |
Приводимый ниже пример является многопоточным приложением, пара дополнительных потоков которого получают доступ к одному и тому же объекту (объекту синхронизации). Результаты воздействия образующих потоки операторов наглядно проявляются на экране консольного приложения.
using System;
using System.Threading;
namespace threads12
{
class TextPresentation
{
public Mutex mutex;
public TextPresentation()
{
mutex = new Mutex();
}
public void showText(string text)
{
int i;
// Объект синхронизации в данном конкретном случае –
// представитель класса TextPresentation. Для его обозначения
// используется первичное выражение this.__________________________
//1. Блокировка кода монитором (начало) // Monitor.Enter(this);
//2. Критическая секция кода (начало)// lock(this) {
mutex.WaitOne();//3.Блокировка кода мьютексом (начало)//
Console.WriteLine("\n" + (char)31 + (char)31 + (char)31 + (char)31);
for (i = 0; i < 250; i++)
{
Console.Write(text);
}
Console.WriteLine("\n" + (char)30 + (char)30 + (char)30 + (char)30);
mutex.ReleaseMutex();//3.Блокировка кода мьютексом (конец)//
//2. Критическая секция кода (конец) // }
//1. Блокировка кода монитором (конец) // Monitor.Exit(this);
}
}
class threadsRunners
{
public static TextPresentation tp = new TextPresentation();
public static void Runner1()
{
Console.WriteLine("thread_1 run!");
Console.WriteLine("thread_1 – calling TextPresentation.showText");
tp.showText("*");
Console.WriteLine("thread_1 stop!");
}
public static void Runner2()
{
Console.WriteLine("thread_2 run!");
Console.WriteLine("thread_2 – calling TextPresentation.showText");
tp.showText("|");
Console.WriteLine("thread_2 stop!");
}
static void Main(string[] args)
{
ThreadStart runner1 = new ThreadStart(Runner1);
ThreadStart runner2 = new ThreadStart(Runner2);
Thread th1 = new Thread(runner1);
Thread th2 = new Thread(runner2);
th1.Start();
th2.Start();
}
}
}
Для получения официальных документов о завершении программы дополнительного профессионального образования (удостоверения о повышении квалификации, дипломов о профессиональной переподготовке и MBA) необходимо предоставить:
Внимание! Вы можете не заказывать доставку бумажной версии официального документы, а скачать его в электронном виде и распечатать самостоятельно. Информация о выданном документе в течение 1 месяца загружается в Федеральную информационную систему «Федеральный реестр сведений о документах об образовании и (или) о квалификации, документах об обучении» - ФИС ФРДО.
Доступ на новый сайт осуществляется с использованием адреса электронной почты, который был указан вами при регистрации на "старом". Мы постарались перенести все ваши данные с прежнего ресурса, однако не исключена вероятность потери части информации.
При возникновении проблемы со входом, воспользуйтесь функцией сброса пароля
Если вы обнаружите несоответствия, пожалуйста, сообщите нам.