// Вывести числа.
Console.Write("Your numbers: ");
for (int i = 0; i < 10; i++)
{
Random r = new Random();
Thread.Sleep(1000 * r.Next(5));
Console.Write("{0}, ", i);
}
Console.WriteLine();
}
finally
{
Monitor.Exit(threadLock);
}
}
Первым делом обратите внимание, что конечным получателем маркера потока, который указывается как аргумент ключевого слова
lock
, является метод
Monitor. Enter()
. Весь код внутри области
lock
помещен внутрь блока
try
. Соответствующий блок
finally
гарантирует освобождение маркера блокировки (посредством метода
Monitor.Exit()
), даже если возникнут любые исключения времени выполнения. Модифицировав программу
MultiThreadShareData
с целью прямого применения типа
Monitor
(как только что было показано), вы обнаружите, что вывод идентичен.
С учетом того, что ключевое слово
lock
требует написания меньшего объема кода, чем при явной работе с типом
System.Threading.Monitor
, может возникнуть вопрос о преимуществах использования этого типа напрямую. Выражаясь кратко, тип
Monitor
обеспечивает большую степень контроля. Применяя тип
Monitor
, можно заставить активный поток ожидать в течение некоторого периода времени (с помощью статического метода
Monitor.Wait()
), информировать ожидающие потоки о том, что текущий поток завершен (через статические методы
Monitor.Pulse()
и
Monitor.PulseAll()
), и т.д.
Как и можно было ожидать, в значительном числе случаев ключевого слова
lock
будет достаточно. Если вас интересуют дополнительные члены класса
Monitor
, тогда обращайтесь в документацию по .NET Core.
Синхронизация с использованием типа System.Threading.Interlocked
Не заглядывая в код CIL, обычно нелегко поверить в то, что присваивание и простые арифметические операции не являются атомарными. По указанной причине в пространстве имен
System.Threading
предоставляется тип, который позволяет атомарно оперировать одиночным элементом данных с меньшими накладными расходами, чем тип
Monitor
. В классе
Interlocked
определены статические члены, часть которых описана в табл. 15.4.
Несмотря на то что это не сразу видно, процесс атомарного изменения одиночного значения довольно часто применяется в многопоточной среде. Пусть имеется код, который инкрементирует целочисленную переменную-член по имени
intVal
. Вместо написания кода синхронизации вроде показанного ниже:
int intVal = 5;
object myLockToken = new();
lock(myLockToken)
{
intVal++;
}
код можно упростить, используя статический метод
Interlocked.Increment()
.
Методу потребуется передать инкрементируемую переменную по ссылке. Обратите внимание, что метод
Increment()
не только изменяет значение входного параметра, но также возвращает полученное новое значение:
intVal = Interlocked.Increment(ref intVal);
В дополнение к методам
Increment()
и
Decrement()
тип
Interlocked
позволяет атомарно присваивать числовые и объектные данные. Например, чтобы присвоить переменной-члену значение
83
, можно обойтись без явного оператора
lock
(или явной логики
Monitor
) и применить метод
Interlock.Exchange()
:
Interlocked.Exchange(ref myInt, 83);
Наконец, если необходимо проверить два значения на предмет равенства и изменить элемент сравнения в безопасной к потокам манере, тогда допускается использовать метод
Interlocked.CompareExchange()
:
public void CompareAndExchange()
{
// Если значение i равно 83, то изменить его на 99.
Interlocked.CompareExchange(ref i, 99, 83);
}
Программирование с использованием обратных вызовов Timer
Многие приложения нуждаются в вызове специфического метода через регулярные интервалы времени. Например, в приложении может существовать необходимость в отображении текущего времени внутри панели состояния с помощью определенной вспомогательной функции. Или, скажем, нужно, чтобы приложение эпизодически вызывало вспомогательную функцию, выполняющую некритичные фоновые задачи, такие как проверка поступления новых сообщений электронной почты. В ситуациях подобного рода можно применять тип
System.Threading.Timer
в сочетании со связанным делегатом по имени
TimerCallback
.
В целях иллюстрации предположим, что у вас есть проект консольного приложения (
TimerApp
), которое будет выводить текущее время каждую секунду до тех пор, пока пользователь не нажмет клавишу <
Enter> для прекращения работы приложения. Первый очевидный шаг — написание метода, который будет вызываться типом
Timer
(не забудьте импортировать в свой файл кода пространство имен
System.Threading
):
using System;
using System.Threading;