Чтение онлайн

ЖАНРЫ

Язык программирования C#9 и платформа .NET5
Шрифт:

Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

– > Worker thread #9 is executing PrintNumbers

Your numbers: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,

Синхронизация с использованием типа System.Threading.Monitor

Оператор

lock
языка C# на самом деле представляет собой сокращение для работы с классом
System.Threading.Monitor
. При обработке компилятором C# область
lock
преобразуется в следующую конструкцию (в чем легко убедиться с помощью утилиты
ldasm.exe
):

public void PrintNumbers

{

Monitor.Enter(threadLock);

try

{

//
Вывести информацию о потоке.

Console.WriteLine("-> {0} is executing PrintNumbers",

Thread.CurrentThread.Name);

// Вывести числа.

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;

Console.WriteLine("***** Working with Timer type *****\n");

Console.ReadLine;

static void PrintTime(object state)

{

Console.WriteLine("Time is: {0}",

DateTime.Now.ToLongTimeString);

}

Обратите внимание, что метод

PrintTime
принимает единственный параметр типа
System.Object
и возвращает
void
. Это обязательно, потому что делегат
TimerCallback
может вызывать только методы, которые соответствуют такой сигнатуре. Значение, передаваемое целевому методу делегата
TimerCallback
, может быть объектом любого типа (в случае примера с электронной почтой параметр может представлять имя сервера Microsoft Exchange Server для взаимодействия в течение процесса). Также обратите внимание, что поскольку параметр на самом деле является экземпляром типа
System.Object
, в нем можно передавать несколько аргументов, используя
System.Array
или специальный класс либо структуру.

Поделиться с друзьями: