Почему временная переменная может помешать клиенту удалить обработчик событий? - PullRequest
1 голос
/ 07 мая 2009

Следующий фрагмент кода взят из книги Effective C #,

public event AddMessageEventHandler Log;

public void AddMsg ( int priority, string msg )

{
    // This idiom discussed below.
    AddMessageEventHandler l = Log;
    if ( l != null )
        l ( null, new LoggerEventArgs( priority, msg ) );
}

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

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

Ответы [ 6 ]

3 голосов
/ 07 мая 2009

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

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

2 голосов
/ 07 мая 2009

Это не останавливает клиента удаление обработчика событий - это просто означает, что если они это сделают, вы в конечном итоге не вызовете нулевой делегат ... рассмотрим:

  • поток A: проверяет, что Log не равен нулю
  • тема B: отписывается, в результате чего Log становится нулевым
  • поток A: вызывает Log (который теперь является нулевым) = boom

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

Как всегда, у Эрика Липперта есть блог на эту тему: События и гонки

2 голосов
/ 07 мая 2009

Цепочки делегатов неизменны. Следовательно, если другой поток обращается к «Log» и удаляет обработчик событий, Log получает новую цепочку делегатов. Следовательно, при обращении к l, даже если обработчик событий удаляется из журнала, он не влияет на l, так как больше не будет «указывать» на ту же цепочку делегатов. Так что да, он защищает от состояния гонки, однако вы можете столкнуться со сценарием, когда один поток отписывается, но evanthandler все равно будет вызываться.

1 голос
/ 07 мая 2009

Клиент все еще может удалить обработчик событий.

Плохо:

if ( Log != null )
{
  //another thread removes the event handler at this point,
  // Log is now null
  Log ( null, new LoggerEventArgs( priority, msg ) );
}

Хорошо:

AddMessageEventHandler l = Log; 
if ( l != null )
  //another thread removes the event handler at this point,
  // Log is now null
  // l is not null, so we are safe.
l ( null, new LoggerEventArgs( priority, msg ) );
0 голосов
/ 07 мая 2009

Текст, который вы набросали, объясняет, что может пойти не так.

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

Второй поток может удалить свой обработчик событий из журнала, когда работает метод AddMsg. Например:

if (Log != null)
{
    // other thread has just removed its event handler, so Log is now null

    Log(null, new LoggerEventArgs(priority, msg)); // Oops! Throws NullReferenceException
}
0 голосов
/ 07 мая 2009

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

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

If(Log != null)
// another thread could null the reference to Log here
l.DoSomething()

AddMessageEventHandler l = Log; 
if ( l != null )
// nothing can null the reference here as you’ve created a local copy of that reference
l ( null, new LoggerEventArgs( priority, msg ) );
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...