Легкая альтернатива Manual / AutoResetEvent в C #

Я написал то, что, как я надеюсь, является легкой альтернативой использованию классов ManualResetEvent и AutoResetEvent в C # /. NET. Причина этого заключалась в том, чтобы иметь функциональность, подобную Event, без необходимости использования объекта блокировки ядра.

Хотя код, кажется, хорошо работает как при тестировании, так и в производстве, правильная реализация этого для всех возможностей может быть сложной задачей, и я смиренно прошу любые конструктивные комментарии и / или критику со стороны толпы StackOverflow по этому поводу. Надеюсь (после обзора) это будет полезно другим.

Использование должно быть аналогично классам Manual / AutoResetEvent с Notify (), используемым для Set ().

Вот оно:

using System;
using System.Threading;

public class Signal
{
  private readonly object _lock = new object();
  private readonly bool _autoResetSignal;
  private bool _notified;

  public Signal()
    : this(false, false)
  {
  }

  public Signal(bool initialState, bool autoReset)
  {
    _autoResetSignal = autoReset;
    _notified = initialState;
  }

  public virtual void Notify()
  {
    lock (_lock)
    {
      // first time?
      if (!_notified)
      {
        // set the flag
        _notified = true;

        // unblock a thread which is waiting on this signal 
        Monitor.Pulse(_lock);
      }
    }
  }

  public void Wait()
  {
    Wait(Timeout.Infinite);
  }

  public virtual bool Wait(int milliseconds)
  {
    lock (_lock)
    {
      bool ret = true;
      // this check needs to be inside the lock otherwise you can get nailed
      // with a race condition where the notify thread sets the flag AFTER 
      // the waiting thread has checked it and acquires the lock and does the 
      // pulse before the Monitor.Wait below - when this happens the caller
      // will wait forever as he "just missed" the only pulse which is ever 
      // going to happen 
      if (!_notified)
      {
        ret = Monitor.Wait(_lock, milliseconds);
      }

      if (_autoResetSignal)
      {
        _notified = false;
      }
      return (ret);
    }
  }
}

person sweetlilmre    schedule 12.05.2010    source источник
comment
Вы сравнивали это с использованием ManualResetEvent / AutoResetEvents? Насколько значительна разница в производительности?   -  person James    schedule 12.05.2010
comment
Нет, я еще не сделал этого, поскольку основная цель заключалась в создании дескриптора / ресурса, отличного от ядра, с использованием объекта события. Я все же попробую провести несколько тестов, спасибо.   -  person sweetlilmre    schedule 12.05.2010
comment
Реализация ваших собственных потоковых примитивов похожа на реализацию ваших собственных криптоалгоритмов. Если вы не являетесь экспертом в данной области, вы можете облажаться. Даже если вы являетесь экспертом, вы все равно можете облажаться. Не делай этого. В .NET 4 уже есть облегченные версии, ManualResetEventSlim и родственные классы.   -  person Aaronaught    schedule 13.05.2010
comment
@Aaronaught: спасибо за то, что я не знал о ManualResetEventSlim, который будет полезен. Тем не менее, я пока ловлюсь на 3.51 ...   -  person sweetlilmre    schedule 13.05.2010
comment
Я бы также проверил этот ответ на мой вопрос о том, что лучше: монитор / импульс или событие ручного сброса.   -  person Matt    schedule 08.04.2013


Ответы (4)


Это работает из предположения, что события Win32 дороги. Это не так, я мало что могу придумать, что дешевле, чем мероприятие. Главный намек на то, что это так, - это то, что разработчики .NET решили, что было бы неплохо использовать событие Win32 для реализации MRE и ARE.

Истинная стоимость вашей замены - это главный FUD, с которым вы столкнетесь, когда у вас будет гонка потоков и вы не знаете, что ее вызывает.

person Hans Passant    schedule 12.05.2010
comment
Предполагается, что ARE и MRE используют событие ядра под капотом, что было бы дороже, чем Monitor. Я бы хотел, чтобы меня доказали, что я ошибаюсь :) - person sweetlilmre; 12.05.2010
comment
В этой статье от Microsoft подробно рассказывается, какие из них быстрые: msdn.microsoft.com/en- us / library / ms228964.aspx - person David d C e Freitas; 19.04.2012
comment
С точки зрения перехода в режим ядра в .NET, события Win32 являются дорогостоящими, отсюда и легкие дополнения в .NET 4 в соответствии с ответом @Aaronaught выше. - person sweetlilmre; 21.11.2012

Один из способов оптимизировать производительность AutoResetEvent - сохранить его состояние (сигнализировано / не сигнализировано) в вашей собственной переменной, поэтому, прежде чем совершать переход в ядро ​​и фактически использовать объект события, вы можете просто проверить состояние своей прикладной переменной и оставаться в ней. все время в пользовательском режиме.
Я разместил демонстрацию об этой концепции пару месяцев назад.

person Liran    schedule 04.11.2010

К сожалению, правильная реализация Monitor довольно тяжеловесна с учетом примитивов синхронизации Win32. Мое первоначальное подозрение состоит в том, что «блокировка» потребует больше ресурсов, чем событие (и, вероятно, построена на событии).

person Stephen Cleary    schedule 12.05.2010
comment
Я почти уверен, что блокировка никоим образом не реализуется скрытым событием. IIRC есть специальные структуры потоков, используемые специально для обработки блокировок потоков и сделать их более легкими, чем события. - person sweetlilmre; 12.05.2010
comment
@sweetlilmre - Я просмотрел реализацию Rotor / SharedCCI, и они действительно используют некоторые блокировки в стиле спин-блокировки, но при необходимости это сводится к типу события, предоставляемому хостом. - person Stephen Cleary; 13.05.2010
comment
Да, они вращаются сами, используя свои собственные биты синхроблока. Единственное, что я не придумал, - это то, как они сообщают планировщику Windows. Есть локальный код? - person Hans Passant; 13.05.2010
comment
Код SyncBlock находится в clr / src / vm / syncblk. * (Класс SyncBlock содержит AwareLock, который выполняет вращение, но передает любую блокировку классу CLREvent). Класс CLREvent находится в clr / src / vm / synch. * И может использоваться для создания различных видов событий, большинство из которых просто вызывает хост через IHostSyncManager. - person Stephen Cleary; 13.05.2010

Как правило, синхронизация выполняется медленно, поэтому лучше избегать ее.

Однако между их скоростью есть большая разница:

Поддерживаемый ЦП Interlocked Exchange является самым быстрым, в то время как простой логический флаг все еще лучше AutoresetEvent.

Ознакомьтесь с полными примерами кода и производительностью. сравнения AutoResetEvent и альтернативы.

person Adam    schedule 07.04.2013