Обложка канала

.NET Разработчик

Опытный разработчик не так давно зашёл в .Net и поставил цель получить сертификат Microsoft. Свой ежедневный прогресс он описывает на канале .Net Разработчик. Заметки об изученном материале, советы по повышению производительности и поддержке мотивации, ин

.NET Разработчик

4 года назад
Открыть в
День 1398. #ЗаметкиНаПолях #AsyncTips Блокирующие и Асинхронные Сигналы 1. Блокирующие Сигналы Задача: требуется отправить уведомление от одного потока другому. Решение Самый распространённый и универсальный межпотоковый сигнал — событие с ручным сбросом ManualResetEventSlim. Оно может находиться в одном из двух состояний: установленном или сброшенном. Любой поток может перевести событие в установленное состояние или сбросить его. Поток также может ожидать перехода события в установленное состояние. Следующие два метода вызываются разными потоками; один поток, использует Wait() и ожидает сигнала от другого, который вызывает Init():
class MyClass
{
  private readonly ManualResetEventSlim 
   _mres = new();
  private int _val;

  public int Wait()
  {
    _mres.Wait();
    return _val;
  }
  public void Init()
  {
    _val = 42;
    _mres.Set();
  }
}

ManualResetEventSlim является синхронным сигналом, поэтому WaitForInitialization блокирует вызывающий поток до отправки сигнала. 2. Асинхронные Сигналы Задача: требуется отправить уведомление от одного потока другому, при этом получатель оповещения должен ожидать его асинхронно. Решение Если уведомление должно быть отправлено только один раз, можно использовать TaskCompletionSource<T>. Код-отправитель вызывает TrySetResult, а код-получатель ожидает его свойство Task:
class MyClassAsync
{
  private readonly TaskCompletionSource<object?> 
   _set = new();
  private int _val1;
  private int _val2;
 
  public async Task<int> WaitAsync()
  {
    await _set.Task;
    return _val1 + _val2;
  }
  public void Init()
  {
    _val1 = 42;
    _val2 = 69;
    _set.TrySetResult(null);
  }
}

TaskCompletionSource<T> может использоваться для асинхронного ожидания любой ситуации — в данном случае уведомления от другой части кода. Этот способ хорошо работает, если сигнал отправляется только один раз, но не работает, если сигнал нужно не только включать, но и отключать. Итого Сигналы представляют собой механизм уведомлений общего назначения, однако использовать их следует только тогда, когда это действительно уместно. Если «сигнал» представляет собой сообщение, отправляющее некоторые данные между потоками, рассмотрите возможность использования очереди «производитель/потребитель». А если сигналы используются только для координации доступа к общим данным, лучше использовать lock или асинхронную блокировку. Источник: Стивен Клири “Конкурентность в C#”. 2-е межд. изд. — СПб.: Питер, 2020. Глава 12.