События C# и потокобезопасность

Возможно, вы изменили значение отступа на вкладке. Посмотрите на строку состояния (внизу справа) и посмотрите, сколько пробелов установлено на вашей вкладке. Вы можете нажать на строку состояния, чтобы изменить ее, или нажать cmd + shift + p (OS X) или ctrl + shift + p (Windows), чтобы открыть команду pallete, и набрать Indent, чтобы найти опцию Indent Using Spaces и установить для нее 4 (или [ 113], как хотите).

230
задан DaveInCaz 20 February 2019 в 02:54
поделиться

8 ответов

JIT не разрешено выполнять оптимизацию, о которой вы говорите в первой часть, из-за состояния. Я знаю, что это было поднято как призрак некоторое время назад, но это не верно. (Я проверял это либо с Джо Даффи, либо с Вэнсом Моррисоном некоторое время назад; я не могу вспомнить, какой именно.)

Без модификатора volatile возможно, что полученная локальная копия будет устаревшей, но это все. Это не вызовет исключение NullReferenceException .

И да, безусловно, есть условие гонки, но оно всегда будет. Предположим, мы просто изменили код на:

TheEvent(this, EventArgs.Empty);

Теперь предположим, что список вызовов для этого делегата содержит 1000 записей. Это' Вполне возможно, что действие в начале списка будет выполнено до того, как другой поток откажется от подписки обработчика ближе к концу списка. Тем не менее, этот обработчик все равно будет выполнен, потому что это будет новый список. (Делегаты являются неизменными.) Насколько я вижу, это неизбежно.

Использование пустого делегата, безусловно, позволяет избежать проверки недействительности, но не устраняет условия гонки. Это также не гарантирует, что вы всегда «видите» последнее значение переменной.

98
ответ дан 23 November 2019 в 03:41
поделиться

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

Я делаю это неправильно?

0
ответ дан 23 November 2019 в 03:41
поделиться

для однопоточных приложений, вы исправляете это не проблема.

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

Использование пустого делегата решает проблему, но также приводит к снижению производительности при каждом вызове события и может иметь последствия для GC.

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

Если вы не используете временную переменную, и не используйте пустой делегат, и кто-то отписывается, вы получаете исключение нулевой ссылки, которое является фатальным, поэтому я думаю, что цена того стоит.

0
ответ дан 23 November 2019 в 03:41
поделиться

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

public static class Extensions   
{   
  public static void Raise<T>(this EventHandler<T> handler, 
    object sender, T args) where T : EventArgs   
  {   
    if (handler != null) handler(sender, args);   
  }   
}

Это дает вам более приятный синтаксис для вызова события ...

MyEvent.Raise( this, new MyEventArgs() );

А также избавляет от локальная копия, поскольку она захватывается во время вызова метода.

50
ответ дан 23 November 2019 в 03:41
поделиться

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

Причины, по которым люди заботятся о исключении с нулевой ссылкой, а не о состоянии гонки, потребуют глубоких психологических исследований. Я думаю, что это как-то связано с тем, что решить проблему нулевых ссылок гораздо проще. Как только это будет исправлено, они вешают большой код «Миссия выполнена» на свой код и расстегивают молнию на своем летном костюме.

Примечание:

1
ответ дан 23 November 2019 в 03:41
поделиться

Так что я немного опоздал на вечеринку здесь. :)

Что касается использования нулевого, а не нулевого шаблона объекта для представления событий без подписчиков, рассмотрим этот сценарий. Вам нужно вызвать событие, но создание объекта (EventArgs) нетривиально, и в общем случае ваше событие не имеет подписчиков. Было бы полезно, если бы вы могли оптимизировать свой код, чтобы проверить, есть ли у вас какие-либо подписчики, прежде чем вы предпримете обработку для построения аргументов и вызова события.

Учитывая это, решение состоит в том, чтобы сказать, «ну, нулевые подписчики представлены нулем». Затем просто выполните нулевую проверку перед выполнением дорогостоящей операции. Я полагаю, что другим способом сделать это было бы иметь свойство Count для типа Delegate, так что вы Выполнять дорогостоящую операцию можно только в том случае, если myDelegate.Count> 0. Использование свойства Count является несколько приятным шаблоном, который решает исходную проблему разрешения оптимизации, а также обладает хорошим свойством возможности вызываться, не вызывая исключение NullReferenceException.

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

Примечание: это чистая спекуляция. Я не связан с языками .NET или CLR.

Использование свойства Count - это довольно приятный шаблон, который решает исходную проблему разрешения оптимизации, и он также обладает хорошим свойством возможности вызываться, не вызывая NullReferenceException.

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

Примечание: это чистая спекуляция. Я не связан с языками .NET или CLR.

Использование свойства Count - это довольно приятный шаблон, который решает исходную проблему разрешения оптимизации, и он также обладает хорошим свойством возможности вызываться, не вызывая NullReferenceException.

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

Примечание: это чистая спекуляция. Я не связан с языками .NET или CLR.

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

Примечание: это чистая спекуляция. Я не связан с языками .NET или CLR.

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

Примечание: это чистая спекуляция. Я не связан с языками .NET или CLR.

Примечание: это чистое предположение. Я не связан с языками .NET или CLR.

Примечание: это чистое предположение. Я не связан с языками .NET или CLR.

1
ответ дан 23 November 2019 в 03:41
поделиться

«Почему явное -null-check the 'standard pattern'? "

Я подозреваю, что причина этого может быть в том, что проверка на null более эффективна.

Если вы всегда подписываете пустой делегат на свои события, когда они создаются, там будут накладными расходами:

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

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

Я провел небольшое тестирование производительности, чтобы увидеть влияние подхода «подписка-пустой-делегат», и вот мои результаты:

Executing 50000000 iterations . . .
OnNonThreadSafeEvent took:      432ms
OnClassicNullCheckedEvent took: 490ms
OnPreInitializedEvent took:     614ms <--
Subscribing an empty delegate to each event . . .
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took:      674ms
OnClassicNullCheckedEvent took: 674ms
OnPreInitializedEvent took:     2041ms <--
Subscribing another empty delegate to each event . . .
Executing 50000000 iterations . . .
OnNonThreadSafeEvent took:      2011ms
OnClassicNullCheckedEvent took: 2061ms
OnPreInitializedEvent took:     2246ms <--
Done

Обратите внимание, что для случая нуля или одного подписчика (обычно для элементов управления пользовательского интерфейса, где события много), событие, предварительно инициализированное пустым делегатом, заметно медленнее (более 50 миллионов итераций ...)

Для получения дополнительной информации и исходного кода посетите это сообщение в блоге на Безопасность потоков при вызове событий .NET , который я опубликовал за день до того, как был задан этот вопрос (!)

(Моя тестовая установка может быть некорректной, поэтому не стесняйтесь загружать исходный код и проверять его самостоятельно. Любые отзывы приветствуются.)

событие, предварительно инициализированное пустым делегатом, значительно медленнее (более 50 миллионов итераций ...)

Для получения дополнительной информации и исходного кода посетите это сообщение в блоге на Безопасность потоков при вызове событий .NET , которое я опубликовано за день до того, как был задан этот вопрос (!)

(Моя тестовая установка может быть некорректной, поэтому не стесняйтесь загружать исходный код и проверять его самостоятельно. Мы будем благодарны за любые отзывы.)

событие, предварительно инициализированное пустым делегатом, значительно медленнее (более 50 миллионов итераций ...)

Для получения дополнительной информации и исходного кода посетите это сообщение в блоге на Безопасность потоков при вызове событий .NET , которое я опубликовано за день до того, как был задан этот вопрос (!)

(Моя тестовая установка может быть некорректной, поэтому не стесняйтесь загружать исходный код и проверять его самостоятельно. Мы будем благодарны за любые отзывы.)

33
ответ дан 23 November 2019 в 03:41
поделиться

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

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

Все, что может быть уведомлено, должно быть уведомлено. Если ваши обработчики событий правильно обрабатывают уведомления (т. Е. Имеют доступ к авторитетному состоянию приложения и отвечают только при необходимости), тогда будет нормально уведомить их в любое время и быть уверенным, что они ответят должным образом.

Единственный раз, когда обработчик не должен быть уведомлен о том, что событие произошло, - это если событие на самом деле не произошло! Поэтому, если вы не хотите, чтобы обработчик получал уведомление, прекратите генерировать события (т.е. отключите элемент управления или что-то еще, что отвечает за обнаружение и запуск события в первую очередь).

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

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

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

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

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

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

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

0
ответ дан 23 November 2019 в 03:41
поделиться
Другие вопросы по тегам:

Похожие вопросы: