Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Общие вопросы по .NET и C# > Вызов событий.


Автор: neutrino 29.6.2008, 11:22
Привет!

Вот, собственно, если написать код:
Код

public event EventHandler someEvent;

protected virtual onSomeEvent()
{
if (someEvent != null) someEvent(this, new EventArgs());
}

То если случился context switch между проверкой и вызовом someEvent, и тот поток, который получил процессорное время отписал обработчик события, то мы получим NullReferenceException, т.к. someEvent уже будет null.

Майкрософт (чтоб она сдохла) нашла "решение" для этой ситуации и даже вписала для него сниппет "invoke":

Код

protected virtual onSomeEvent()
{
EventHandler temp = someEvent;
if (temp != null)
{
temp(this, new EventArgs());
}
}

Гы smile Прикольно? То есть если даже обработчик и будет отписан, он все равно вызовется. Оригинально!

Автор: Bogdan1024 29.6.2008, 12:32
Цитата
То если случился context switch между проверкой и вызовом someEvent, и тот поток, который получил процессорное время отписал обработчик события, то мы получим NullReferenceException, т.к. someEvent уже будет null.
Круто. Никогда не думал что такое может случиться.

Добавлено через 10 минут и 50 секунд
Наверно, можно ещё lock'чить someEvent чтобы никто от него не мог отписываться.

Автор: Idsa 29.6.2008, 12:47
neutrino, а разве переменная temp не будет ссылаться на тот же экземпляр делегата? Если так, то временная переменная бессмысленна.

Автор: Bogdan1024 29.6.2008, 13:45
Я проверял кодом: реально никто ни на кого ссылаться не будет.
Код

using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication2
{
    class Program
    {
        private static event EventHandler eh1;
        static void Main(string[] args)
        {
            eh1 += new EventHandler(Do);
            EventHandler eh2 = eh1;
            eh1 -= new EventHandler(Do);
            eh2.Invoke(null, null);
            System.Console.ReadKey(true);
        }

        private static void Do(object sender, EventArgs ea)
        {
            System.Console.WriteLine("Do");
        }
    }
}


Автор: Idsa 29.6.2008, 14:19
neutrino, спасибо за интересную тему. +1

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

Автор: neutrino 29.6.2008, 15:12
Цитата(Bogdan1024 @  29.6.2008,  11:32 Найти цитируемый пост)
Наверно, можно ещё lock'чить someEvent чтобы никто от него не мог отписываться.

Да. Можно. Но вот представь себе сколько событий бывает... Да и performance... Можно ведь делать что-то типа:

Код

        private readonly object syncronizer = new object();

        private MouseEventHandler m_beginDragging;

        public event MouseEventHandler BeginDragging
        {
            add
            {
                lock (syncronizer)
                    m_beginDragging += value;
            }
            remove
            {
                lock (syncronizer)
                    m_beginDragging -= value;
            }
        }

        protected virtual void OnBeginDragging(MouseEventArgs e)
        {
            lock (syncronizer)
            {
                if (m_beginDragging != null)
                    m_beginDragging(this, e);
            }
        }


Ну это так "навскидку". Сейчас будем искать неточности и баги...

Автор: marcusmae 29.6.2008, 17:01
(глюк)

Автор: marcusmae 29.6.2008, 17:01
Цитата(neutrino @  29.6.2008,  11:22 Найти цитируемый пост)
случился context switch между проверкой и вызовом someEvent, и тот поток, который получил процессорное время отписал обработчик события


Читаем http://guerillatactics.co.uk/blog/archive/2008/03/23/alternative-way-to-raise-events.aspx :

Код

if (someEvent != null)
{
    someEvent(this, someEventArgs);
}


Цитата

This is partially right and in a single threaded environment will work fine but this code is not actually thread safe. If you imagine that the event is basically just a list of delegates and if you consider that your application may be using multiple threads then it is possible that between you performing the check for null and actually invoking the event the list may have changed. For example there may have been one delegate in there at the time of the check which is not there when the invoke occurs causing an exception to be thrown.


Цитата

Для некоторых ситуаций это подходит : в однопоточном приложении этот код будет отлично работать, однако он не является tread safe. Если представить, что событие - это всего лишь список делегатов и ваше приложение может использовать несколько потоков, то возможно, что между проверкой на null и самим возникновением события, список делегатов может измениться. Например, на момент проверки имеется зарегестрированный делегат, а на момент вызова - он уже снят, что в результате приведёт к возникновению исключения. 


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

Код

EventHandler temp = someEvent;

if (temp != null)
{
    temp(this, someEventArgs);
}


Цитата(neutrino @  29.6.2008,  11:22 Найти цитируемый пост)
Майкрософт (чтоб она сдохла) нашла "решение" для этой ситуации и даже вписала для него сниппет "invoke":


Тут Вы горячитесь. Что имеется в виду : 

Цитата

A thread safe way to invoke events is to make a copy of the delegate list and use that. In the past I've always done this anyway even if I'm in a single threaded app partially out of habit and partially because it means I can potentially introduce threading later without having to worry about my events.


Цитата

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


То есть смысл копирования в потокобезопасности, а именно :

Код

            EventHandler<EventArgs> temp = someEvent;
            if(temp != null) {
                // например, тут влезает другой поток:
                someEvent = null;
                
                temp(this, EventArgs.Empty); // ? Почему вызывается SimpleCallback(), а не throw NullRefferenceException ?
            }


Цитата

В куче лежит объект типа EventHandler<EventArgs> назовем его A. someEvent указывает на A. После temp = someEvent, temp указывает на А, а не на someEvent.

someEvent = null; указывает на null, а temp все также на А.


То есть, если someEvent изменится, а А - нет, то temp не изменится.

Цитата http://www.gotdotnet.ru/Forums/Common/529713.aspx.

Автор: Idsa 2.7.2008, 16:59
Цитата(marcusmae @  29.6.2008,  21:01 Найти цитируемый пост)

То есть, если someEvent изменится, а А - нет, то temp не изменится.

С Вашим примером все понятно: там другая ссылка присваивается. А вот с пониманием этого кода:
Цитата(Bogdan1024 @  29.6.2008,  17:45 Найти цитируемый пост)

            eh1 += new EventHandler(Do);
            EventHandler eh2 = eh1;
            eh1 -= new EventHandler(Do);
            eh2.Invoke(null, null);
            System.Console.ReadKey(true);

у меня возникли проблемы. Ведь здесь и eh1, и eh2 указывают на один и тот же объект в памяти. А ведут себя, как будто копировались по значению.

А вообще, правда, не проще ли было завернуть if с проверкой в lock?!

Автор: PashaPash 2.7.2008, 18:16
Цитата(Idsa @  2.7.2008,  16:59 Найти цитируемый пост)
Ведь здесь и eh1, и eh2 указывают на один и тот же объект в памяти. А ведут себя, как будто копировались по значению.
Вообще-то операции += и -= создают новые объекты:
Код
private static void Main(string[] args)
{
    EventHandler eh2;
    eh1 = (EventHandler) Delegate.Combine(eh1, new EventHandler(Program.Do));
    eh2 = eh1;
    eh1 = (EventHandler) Delegate.Remove(eh1, new EventHandler(Program.Do));
    eh2(null, null);
    Console.ReadKey(1);
    return;
}
Для не-делегатов, кстати, +=/-= себя тоже так ведут.
Цитата(Idsa @  2.7.2008,  16:59 Найти цитируемый пост)
А вообще, правда, не проще ли было завернуть if с проверкой в lock?!
Создать ссылку в стеке намного проще и дешевле чем сделать lock.

Автор: marcusmae 2.7.2008, 19:59
Цитата(Idsa @  2.7.2008,  16:59 Найти цитируемый пост)
А вообще, правда, не проще ли было завернуть if с проверкой в lock?!


Цитата(PashaPash @  2.7.2008,  18:16 Найти цитируемый пост)
Создать ссылку в стеке намного проще и дешевле чем сделать lock.


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

Автор: neutrino 3.7.2008, 08:04
marcusmae, Да я этo все понимаю. Нo вот получается, чтo если обработчик события отписался (между if и вызовом) то он все равно вызовется.

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)