Добавление и удаление обработчиков событий для многопоточных приложений

У меня есть вопрос, как правильно добавить / удалить обработчик событий в многопоточной среде для асинхронных обратных вызовов.

У меня есть класс MyCore, который получает асинхронные обратные вызовы от ProxyDLL, который отправляет обратный вызов из неуправляемого кода. У меня есть форма (управляемая), которая подписывается на события.

Каков был бы правильный подход к присоединению / отсоединению от события. Я заметил, что MulticastDelegate имеет _invocationcount. Что оно делает? Внутренняя логика блокирования событий отсоединяется от события, если выполняется обратный вызов, пока обратный вызов не будет выполнен? Существует ли _invocationcount для этой куколки? Является ли детальность от события (в целом) безопасным?

class Form1
{
  EventHandler m_OnResponse;
  Int32 m_SomeValue;
  Form1()
  {
    m_OnResponse = new EventHandler(OnResponseImpl);
    m_MyCore.SetCallBackOnLogOn(m_OnResponse);
  }
  ~Form1()
  {
    m_MyCore.ReleaseCallBackOnLogOn(m_OnResponse);
  }
  private OnResponseImpl(object sender, EventArgs e)
  {
    Thread.Sleep(60*1000);

    m_SomeValue = 1;             // <<-- How to/Who guarantees that Form1 obj is still
                                 // alive. May be callback was invoked earlier and
                                 // we just slept too long

    if (!this.IsDisposed)
    {
        invokeOnFormThread(DoOnResponseImpl, sender, e);
    }
  }
}

class MyCore
{
  private event EventHandler OnLogOn;
  public void SetCallBackOnLogOn(EventHandler fn)
  {
    // lock (OnLogOn)
    {
        OnLogOn += fn;
    }
  }
  ReleaseCallBackOnLogOn(EventHandler fn)
  {
    // lock (OnLogOn)
    {
        OnLogOn -= fn;
    }
  }
  public void DoDispatchOnLogOn()
  {
    // lock (OnLogOn)
    {
      if (OnLogOn != null)
      {
        OnLogOn(this, null);
      }
    }
  }
}

1 ответ

Решение

Операции добавления и удаления событий по умолчанию уже поточнобезопасны. Вам не нужно беспокоиться об этой части в большинстве случаев. Это вызов многоадресного делегата, о котором вам нужно беспокоиться.

public void DoDispatchOnLogOn()
{
  EventHander local;
  lock (this)
  {
    local = OnLogOn;
  }
  if (local != null)
  {
    local(this, null);
  }
}

Здесь я создал локальную переменную, которая будет содержать OnLogOn цепочка делегатов. Здесь я использую неизменность делегатов многоадресной рассылки, чтобы мы могли выполнить потокобезопасную проверку на нулевое значение и последовательность вызовов. lock используется только для обеспечения "свежего" чтения OnLogon и это строго необязательно, если вы не против получить "устаревшее" чтение.

Обновить:

Мне нужно быть уверенным, что обратный вызов не вызывается, когда ReleaseCallBackOnLogOn завершает отписку делегата.

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

Я должен быть уверен, что мой экземпляр класса все еще жив, пока вызов обратного вызова не будет полностью выполнен.

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

Я должен отметить, что ~Form1 финализатор не является хорошим местом для удаления обработчиков событий. Помните, что делегаты содержат ссылку на экземпляр, содержащий целевой метод, поэтому в большинстве случаев финализатор не будет вызван, а обработчик события не будет удален из события.

Другие вопросы по тегам