Литмир - Электронная Библиотека
A
A

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

    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.

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

Несмотря на то что это не сразу видно, процесс атомарного изменения одиночного значения довольно часто применяется в многопоточной среде. Пусть имеется код, который инкрементирует целочисленную переменную-член по имени

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;

286
{"b":"847442","o":1}