Рейтинговые книги
Читем онлайн Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен

Шрифт:

-
+

Интервал:

-
+

Закладка:

Сделать
1 ... 184 185 186 187 188 189 190 191 192 ... 407
Выражаясь кратко, тип 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 или специальный класс либо структуру.

Следующий шаг связан с конфигурированием экземпляра делегата TimerCallback и передачей его объекту Timer. В дополнение к настройке делегата TimerCallback конструктор Timer позволяет указывать необязательный информационный параметр для передачи целевому методу делегата (определенный как System.Object), интервал вызова метода и период ожидания (в миллисекундах), который должен истечь перед первым вызовом. Вот пример:

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

// Создать делегат для типа Timer.

TimerCallback timeCB = new TimerCallback(PrintTime);

// Установить параметры таймера.

Timer t = new Timer(

  timeCB,     // Объект делегата TimerCallback.

  null,       // Информация для передачи в вызванный метод.

              // (null, если информация отсутствует).

  0,          // Период ожидания перед запуском (в миллисекундах).

  1000);      // Интервал между вызовами (в миллисекундах).

Console.WriteLine("Hit Enter key to terminate...");

Console.ReadLine();

В этом случае метод PrintTime() вызывается приблизительно каждую секунду и не получает никакой дополнительной информации. Ниже показан вывод примера:

***** Working with Timer type *****

Hit key to terminate...

Time is: 6:51:48 PM

Time is: 6:51:49 PM

Time is: 6:51:50 PM

Time is: 6:51:51 PM

Time is: 6:51:52 PM

Press any key to continue ...

Чтобы передать целевому методу делегата какую-то информацию, необходимо просто заменить значение null во втором параметре конструктора подходящей информацией, например:

// Установить параметры таймера.

Timer t = new Timer(timeCB, "Hello From C# 9.0", 0, 1000);

You can then obtain the incoming data as follows:static void PrintTime(object state)

{

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

    DateTime.Now.ToLongTimeString(), state.ToString());

}

Использование автономного отбрасывания (нововведение в версии 7.0)

В предыдущем примере переменная Timer не применяется в каком-либо пути выполнения и потому может быть заменена отбрасыванием:

 var _ = new Timer(

    timeCB,  // Объект делегата TimerCallback.

    null,    // Информация для передачи в вызванный метод

             // (null, если информация отсутствует).

    0,       // Период ожидания перед запуском

             // (в миллисекундах).

    1000);   // Интервал между вызовами

             // (в миллисекундах). 

Класс ThreadPool

Следующей темой о потоках, которую мы рассмотрим в настоящей главе, будет роль пула потоков. Запуск нового потока связан с затратами, поэтому в целях повышения

1 ... 184 185 186 187 188 189 190 191 192 ... 407
На этой странице вы можете бесплатно читать книгу Язык программирования C#9 и платформа .NET5 - Эндрю Троелсен бесплатно.

Оставить комментарий