Сразу скажу, что статья не про потоки, а про события в контексте потоков в .NET. Поэтому я не буду пытаться организовать работу потоков правильно (со всеми блокировками, колбэками, отменой и тд). Для правильной организации потоков есть другие статьи.

Все примеры будут написаны на языке C# для версии фреймворка 4.0 (на 4.6 все несколько проще, но все еще есть много проектов на 4.0). Так же буду пытаться придерживаться версии C# 5.0.

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

Способ 1
    class WrongRaiser
    {
        public event Action<object> MyEvent;
        public event Action MyEvent2;
    }

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

Способ 2
    class WrongRaiser
    {
        public event MyDelegate MyEvent;
    }

    class MyEventArgs
    {
        public object SomeProperty { get; set; }
    }

    delegate void MyDelegate(object sender, MyEventArgs e);

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

Теперь о том, что уже создано для событий.

Универсальный способ
    class Raiser
    {
        public event EventHandler<MyEventArgs> MyEvent;
    }

    class MyEventArgs : EventArgs
    {
        public object SomeProperty { get; set; }
    }

Как видите, здесь мы используем универсальный класс EventHandler. То есть определять собственный хандлер нет необходимости.

Есть и еще способ — создать новый класс, унаследованный от Delegate или MulticastDelegate, но это уже для совсем специфичных случаев. Фактически, способ 2 это и делает за кулисами.

Для дальнейших примеров будет использован универсальный способ.

Посмотрим на простейший пример генератора событий

Пример
    class EventRaiser
    {
        int _counter;

        public event EventHandler<EventRaiserCounterChangedEventArgs> CounterChanged;

        public int Counter
        {
            get
            {
                return _counter;
            }

            set
            {
                if (_counter != value)
                {
                    var old = _counter;
                    _counter = value;
                    OnCounterChanged(old, value);
                }
            }
        }

        public void DoWork()
        {
            new Thread(new ThreadStart(() =>
            {
                for (var i = 0; i < 10; i++)
                    Counter = i;
            })).Start();
        }

        void OnCounterChanged(int oldValue, int newValue)
        {
            if (CounterChanged != null)
                CounterChanged.Invoke(this, new EventRaiserCounterChangedEventArgs(oldValue, newValue));
        }
    }

    class EventRaiserCounterChangedEventArgs : EventArgs
    {
        public int NewValue { get; set; }
        public int OldValue { get; set; }
        public EventRaiserCounterChangedEventArgs(int oldValue, int newValue)
        {
            NewValue = newValue;
            OldValue = oldValue;
        }
    }

Имеем класс, который имеет свойство Counter и умеет изменять его от 0 до 10. Причем логика, которая меняет Counter, обрабатывается в отдельном потоке.

А вот наша точка входа.

    class Program
    {
        static void Main(string[] args)
        {
            var raiser = new EventRaiser();
            raiser.CounterChanged += Raiser_CounterChanged;
            raiser.DoWork();
            Console.ReadLine();
        }

        static void Raiser_CounterChanged(object sender, EventRaiserCounterChangedEventArgs e)
        {
            Console.WriteLine(string.Format("OldValue: {0}; NewValue: {1}", e.OldValue, e.NewValue));
        }
    }

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

Вот что мы получим в результате



Пока все ровно. Но подумайте, а в каком потоке выполняется обработчик события?

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

Устройство делегатов
Класс Delegate имеет информацию о методе.
Есть еще его наследник MulticastDelegate, который имеет более одного элемента.

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

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

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

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

        void OnCounterChanged(int oldValue, int newValue)
        {
            if (CounterChanged != null)
            {
                CounterChanged.Invoke(this, new EventRaiserCounterChangedEventArgs(oldValue, newValue));
                Console.WriteLine(string.Format("Event Raiser: old = {0}, new = {1}", oldValue, newValue));
            }
                
        }

Обработчик
        static void Raiser_CounterChanged(object sender, EventRaiserCounterChangedEventArgs e)
        {
            Console.WriteLine(string.Format("OldValue: {0}; NewValue: {1}", e.OldValue, e.NewValue));
            Thread.Sleep(500);
        }

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

Но вот что мы видим на самом деле.



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

Примечание
И метод Invoke и метод BeginInvoke не являются членами класса Delegate или MulticastDelegate, они являются членами сгенерированного класса (или для универсального класса уже описанного класса).

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

Многопоточная генерация событий
        void OnCounterChanged(int oldValue, int newValue)
        {
            if (CounterChanged != null)
            {
                var delegates = CounterChanged.GetInvocationList();
                for (var i = 0; i < delegates.Length; i++)
                    ((EventHandler<EventRaiserCounterChangedEventArgs>)delegates[i]).BeginInvoke(this, new EventRaiserCounterChangedEventArgs(oldValue, newValue), null, null);
                Console.WriteLine(string.Format("Event Raiser: old = {0}, new = {1}", oldValue, newValue));
            }
                
        }

Два последних параметра у нас равны null. Первый — это колбэк, второй — некий параметр. Колбэк в данном примере я не использую, потому что пример промежуточный. Он может пригодится для обратной связи, например, чтобы класс, генерирующий событие, мог узнать, было ли обработано событие и/или, если нужно, получить результаты этой обработки, а так же, как подсказали в комментах, освобождает ресурсы, связанные с асинхронной операцией.

Если запустим программу, получим такой результат



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

Тут возникает еще вопрос, а как же последовательная обработка? У нас же Counter. А что если это была бы последовательная смена состояний? Но ответ на этот вопрос я вам не дам, это не касается темы текущей статьи. Могу лишь сказать, что способов несколько.

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

Класс для генерации асинхронных событий
    static class AsyncEventsHelper
    {
        public static void RaiseEventAsync<T>(EventHandler<T> h, object sender, T e) where T : EventArgs
        {
            if (h != null)
            {
                var delegates = h.GetInvocationList();
                for (var i = 0; i < delegates.Length; i++)
                    ((EventHandler<T>)delegates[i]).BeginInvoke(sender, e, h.EndInvoke, null);
            }
        }
    }

В данном случае мы используем колбэк. Выполняется он в том же потоке, что и обработчик. То есть после того, как метод обработчика завершился, делегат вызывает следом h.EndInvoke.

Используем его вот так

        void OnCounterChanged(int oldValue, int newValue)
        {
            AsyncEventsHelper.RaiseEventAsync(CounterChanged, this, new EventRaiserCounterChangedEventArgs(oldValue, newValue)); 
        }

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

Заключение


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

Жду комментариев с предложениями, дополнениями, вопросами.
Поделиться с друзьями
-->

Комментарии (94)


  1. mayorovp
    10.02.2017 10:20
    +3

    И в чем же преимущество EventHandler<> над Action или MyDelegate?


    Есть и еще способ — создать новый класс, унаследованный от Delegate или MulticastDelegate, но это уже для совсем специфичных случаев. Фактически, способ 2 это и делает за кулисами.

    Как вы вообще это себе представляете?
    error CS0644: 'ExampleHandler' cannot derive from special class 'MulticastDelegate'


    1. MoreBeauty
      10.02.2017 10:26

      Я ведь в конце об этом написал. Смотрите на класс AsyncEventsHelper. Такой фокус бы не удался при использовании Action или MyDelegate. А в большинстве случаев (по моим личным наблюдениям) данный способ оповещения вполне подходит.

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


      1. mayorovp
        10.02.2017 10:29

        При использовании Action<> с одним параметром такой фокус делается столь же просто.


        1. MoreBeauty
          10.02.2017 10:30

          Да, а что если одно событие у вас принимает string, а другое int?


          1. mayorovp
            10.02.2017 10:32

            Если событие что-то возвращает — то у него, во-первых, не может быть более 1 подписчика — а во-вторых, нам в любом случае надо дождаться результата. В итоге никакой AsyncEventsHelper попросту не нужен.


            1. werwolfby
              10.02.2017 10:42

              Может быть несколько подписчиков, просто результут вернётся только из последнего


              1. mayorovp
                10.02.2017 10:45

                Неявно забытое возвращаемое значение — это ошибка. Если его старались-возвращали — значит, оно кому-то было нужно.


                1. werwolfby
                  10.02.2017 10:48

                  Оно не забытое оно потерянное :)
                  Но конечно впринципе использовать события для возврата значения хоть и известная практика, но она плохая именно по этой причине.


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


            1. MoreBeauty
              10.02.2017 10:46

              я же исправил. принимает а не возвращает… или имеет аргументы если хотите…


              1. mayorovp
                10.02.2017 10:47
                +1

                Я ответил до того как вы исправили.


          1. lair
            10.02.2017 10:33
            +1

            Дженерик же.


            1. MoreBeauty
              10.02.2017 10:45
              -3

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


              1. mayorovp
                10.02.2017 10:49
                +1

                Реализация EventArgs:


                [Serializable]
                [System.Runtime.InteropServices.ComVisible(true)]
                public class EventArgs {
                    public static readonly EventArgs Empty = new EventArgs();
                
                    public EventArgs() 
                    {
                    }
                }

                Да, действительно, написать свой собственный аналог этого класса — так сложно!


                Вы понимаете, что у приведенного вами же класса MyEventArgs можно "оторвать" базовый тип — и от этого ничего не изменится (после перехода на Action)?


                1. MoreBeauty
                  10.02.2017 10:57

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


                  1. mayorovp
                    10.02.2017 10:58
                    +2

                    Вообще-то, это был мой вопрос.


                    А зачем? Зачем использовать EventHandler-то?


                    1. MoreBeauty
                      10.02.2017 10:59
                      -2

                      Ну да… это как вопрос «а зачем мне использовать готовый велосипед, я изобрету свой»… Да пожалуйста


                      1. mayorovp
                        10.02.2017 11:01
                        +3

                        Почему стандартный класс Action<> называется велосипедом? С каким пор ключевое слово delegate стало признаком велосипеда?


                        1. MoreBeauty
                          10.02.2017 11:10
                          -2

                          Класс Action<> не является велосипедом. Только в данном случае он неуместен. Это как «string1» + «string2» + «string3».


                          1. lair
                            10.02.2017 11:11
                            +3

                            Класс Action<> не является велосипедом. Только в данном случае он неуместен.

                            Почему?


                  1. lair
                    10.02.2017 10:58
                    +1

                    Чтобы не писать EventHandler вокруг одного строкового значения.


              1. lair
                10.02.2017 10:52
                +2

                Ну хорошо, а если надо больше аргументов события?

                Берется любой класс, в чем проблема-то?


                1. MoreBeauty
                  10.02.2017 10:58

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


                  1. lair
                    10.02.2017 11:00
                    +2

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

                    Задача неполна, поэтому и решения ей быть не может. Если вам надо fire-and-forget — берете очередь и пишете в нее, все остальное вас не волнует.


                    1. MoreBeauty
                      10.02.2017 11:13
                      -1

                      Так… Задача вполне конкретна. у меня есть генератор событий, который принимает хандлер типа EventHandler TEventArgs и с ним работает. А в случае с Action<> у вас будет 19 таких генераторов по количеству разновидностей Action. То есть
                      для Action

                      для Actionдля Action<T, T>
                      и тд до 18 аргументов


                      1. lair
                        10.02.2017 11:16
                        +2

                        Задача вполне конкретна. у меня есть генератор событий, который принимает хандлер типа EventHandler и с ним работает. А в случае с Action<> у вас будет 19 таких генераторов по количеству разновидностей Action.

                        Неа. Я просто ограничу "события" Action<T> и все.


                        (собственно, в Rx так и сделано)


                        1. MoreBeauty
                          10.02.2017 11:17
                          -1

                          А что у вас будет в качестве T?


                          1. lair
                            10.02.2017 11:19
                            +1

                            В смысле "будет ли он ограничен"? Нет, не будет. Зачем?


                            1. MoreBeauty
                              10.02.2017 11:27

                              в смысле какого типа аргумент будет иметь ваше событие


                              1. lair
                                10.02.2017 11:34
                                +1

                                Ну так от события зависит. Если у меня конкретное событие принимает строку, будет Action<string>, если http-запрос — Action<HttpRequestMessage>, ну и так далее.


                                1. MoreBeauty
                                  10.02.2017 12:42

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

                                  Action<object, MyClass>

                                  Action — это тоже делегат. и казалось бы, почему бы тогда не использовать его?

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

                                  SomeNetEventHandler: EventHandler<SomeNeteventArgs>

                                  и тут мой универсальный метод будет кстати. Я ответил на ваш вопрос?


                                  1. lair
                                    10.02.2017 12:52
                                    +2

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

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


                                    Action<object, MyClass>

                                    Достаточно Action<T>, где T определяется каждым событием, либо Action<TSource,TArgs>, если я хочу передавать источник.


                                    Вы, кстати, не представляете себе, как меня достал object sender в стандартных событиях.


                                    Что, если вы захотите породить событие, описанное где то в дебрях фрэймворка?

                                    Напишу адаптер: (Action<T>) (T args) => handler(this, args).


                                    Другое дело, что не захочу.


                                    Я ответил на ваш вопрос?

                                    Эээ, простите, какой вопрос?


                                    1. MoreBeauty
                                      10.02.2017 13:06

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

                                      Достаточно Action<T>, где T определяется каждым событием, либо Action<TSource,TArgs>, если я хочу передавать источник.

                                      Вы, кстати, не представляете себе, как меня достал object sender в стандартных событиях.


                                      Почему же не представляю? Представляю. Но иногда за универсальность приходится платить.
                                      Иногда один обработчик может использоваться для аналогичных событий разных типов. И если породитель события нам в данном случае не интересен, то object sender мы оставим без внимания, а вместо этого обработаем аргументы события.

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


                                      1. lair
                                        10.02.2017 13:08
                                        +1

                                        Но иногда за универсальность приходится платить.

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


  1. werwolfby
    10.02.2017 10:23
    +3

    Вы же вкурсе что на каждый Begin<Method> должен быть свой End<Method>? ;)


    1. MoreBeauty
      10.02.2017 10:30

      Я в курсе, что есть такой функционал. Нужно это для ожидания завершения и получения результата. Но ИвентХандлеры возвращают void. Поэтому, если дожидаться выполнения работы обработчика нам не нужно, использовать EndInvoke незачем.

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


      1. mayorovp
        10.02.2017 10:35
        +5

        Вообще-то, метод EndInvoke еще и освобождает ресурсы!


        Если вам не нужен результат — то существует очень простое решение:


        var d = (EventHandler<T>)delegates[i];
        d.BeginInvoke(sender, e, d.EndInvoke, null);

        А пропускать вызов End-метода не следует.


        1. MoreBeauty
          10.02.2017 11:31

          Согласен. Спасибо за инфу. Сейчас поправлю статью.


      1. lair
        10.02.2017 10:37
        +1

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

        Угу.


        "No matter which technique you use, always call EndInvoke to complete your asynchronous call." (MSDN)


      1. werwolfby
        10.02.2017 10:38

        Для начала можно почитать документацию: https://msdn.microsoft.com/en-us/library/2e08f6yc(v=vs.110).aspx
        Вот цитата с доки:


        No matter which technique you use, always call EndInvoke to complete your asynchronous call.

        Далее если спросить у гугла, то вам явно ответят на stakcoverflow, что таки да, вы ОБЯЗАНЫ вызывать EndInvoke.


        Есть исключение в WinForms приложенях, там вызывать EndInvoke не обязательно, но об этом тоже явно написано в документации.


  1. lair
    10.02.2017 10:32
    +5

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

    Круто, а теперь ответьте на свой же вопрос: в каком потоке выполняется событие?


    Вот, значит, кто-то подписал на ваше событие две сотни обработчиков. Сколько будет потоков? Откуда они возьмутся? А откуда они возьмутся при втором вызове того же события?


    Колбэк в данном примере нам не нужен.

    Агу. А что будет, если в обработчике возникнет ошибка (exception кто-то бросит)?


    1. MoreBeauty
      10.02.2017 10:38
      -3

      Круто, а теперь ответьте на свой же вопрос: в каком потоке выполняется событие?

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

      Вот, значит, кто-то подписал на ваше событие две сотни обработчиков. Сколько будет потоков? Откуда они возьмутся? А откуда они возьмутся при втором вызове того же события?
      Я на старте еще написал, что речь тут не про потоки, а про события в контексте потоков. Я тут не занимался разруливанием потоков. Если вам необходимо рулить пулом, шелдером или еще чем, поищите информацию сами. Я лишь дал идею.

      Агу. А что будет, если в обработчике возникнет ошибка (exception кто-то бросит)?
      Во-первых, в моем примере генератора событий вообще не волнует как это событие будет обработано. поэтому, если возникнет ошибка, то обрабатывать ее надо в методе-обработчике. В случае моего примера это
              static void Raiser_CounterChanged(object sender, EventRaiserCounterChangedEventArgs e)
              {
                  Console.WriteLine(string.Format("OldValue: {0}; NewValue: {1}", e.OldValue, e.NewValue));
              }
      


      1. lair
        10.02.2017 10:44
        +3

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

        Так в каком же?


        Я на старте еще написал, что речь тут не про потоки, а про события в контексте потоков. Я тут не занимался разруливанием потоков. Если вам необходимо рулить пулом, шелдером или еще чем, поищите информацию сами. Я лишь дал идею.

        Так вот, без знания ответов на эти вопросы ваша идея опасна.


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

        Так что же будет, если обработчик не обработает ошибку, и она из него вылетит?


        1. MoreBeauty
          10.02.2017 10:50
          -3

          Так в каком же?
          В новом
          Так вот, без знания ответов на эти вопросы ваша идея опасна.
          Идея тут не может быть опасна. Опасна реализация. Но я же предупредил, что все примеры чисто тестовые. Я тут рассматриваю абстрактного коня в вакууме. Вот если бы я написал библиотеку и выложил на гитхаб, то данное замечание было бы уместно.

          Так что же будет, если обработчик не обработает ошибку, и она из него вылетит?
          Тут все зависит от того, пытается ли обработчик лезть в другие потоки. Если он тихонечко работает в своем конткесте, то с больше долей вероятности он просто упадет и никому об этом не расскажет :)
          Если вам все таки хочется об этом узнать, оборачивайте операции обработчика в try catch


          1. lair
            10.02.2017 10:57
            +3

            В новом

            Откуда он взялся? Какой у него оверхед?


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


            Идея тут не может быть опасна.

            Может, может (я даже не буду приводить очевидные примеры). В вашем случае идея выглядит как "а давайте запустим все обработчики каждый в своем потоке". Первый же вопрос к это идее — это что делать, если обработчиков очень много.


            Тут все зависит от того, пытается ли обработчик лезть в другие потоки.

            Почему? Какая разница?


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

            Так с большой долей вероятности или гарантированно? А не может так случиться, что он упадет и погребет все приложение?


            1. MoreBeauty
              10.02.2017 11:18

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

              Может, может (я даже не буду приводить очевидные примеры). В вашем случае идея выглядит как «а давайте запустим все обработчики каждый в своем потоке». Первый же вопрос к это идее — это что делать, если обработчиков очень много.
              я не предлагал запускать в отдельных потоках, я предложил запустить в потоке отличном от потока работы генератора.

              Почему? Какая разница?
              Потому что это не обсервер

              Так с большой долей вероятности или гарантированно? А не может так случиться, что он упадет и погребет все приложение?
              Не имеет значение… обработка ошибок обработчика событий — дело обработчика событий а не их генератора.


              1. lair
                10.02.2017 11:20
                +3

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


                1. MoreBeauty
                  10.02.2017 13:33

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


                  1. lair
                    10.02.2017 13:41
                    +1

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

                    Для этого достаточно двух фраз: "Обработчики событий — это делегаты. Делегаты выполняются там, откуда их вызвали".


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


  1. Sane
    10.02.2017 10:35
    +2

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


    1. mayorovp
      10.02.2017 10:37
      +1

      BeginInvoke на делегате тоже использует поток пула, нет никакого отличия от ThreadPool.QueueWorkItem


      Запоминание SynchronizationContext в событиях — отдельная проблема, решение которой больше этого поста раза в два :)


    1. MoreBeauty
      10.02.2017 10:40
      -2

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


  1. ijsgaus
    10.02.2017 10:50

    Использовать механизм event в многопоточной среде… Скажем так, один из лучших способов стрельнуть себе даже не в ногу, а в голову. На мой взгляд, event — введенный в язык сахар для обслуживания ui. Более безопасный и управляемый способ — IObservable (+ Reactive Extensions FromEventPattern как переходник). После этого имеем все радости управляемой многонитевой подписки с реактивным потоком событий.


    1. MoreBeauty
      10.02.2017 10:53
      -2

      События и обсерверы имеют разные области применения. Использовать паттерн «наблюдатель», на мой взгляд, имеет смысл, когда генератору событий нужно знать, кто ожидает оповещений и/или выбирать, кому отправить данное оповещение, а кому нет. Мне вот сбербанк отправляет информацию о моих операциях, а вам о моих не отправляет… зато отправляет о ваших… Вот хороший пример обсервера.


      1. lair
        10.02.2017 10:57
        +1

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

        Вообще-то в Observables источник данных понятия не имеет, кто на него подписан.


        1. MoreBeauty
          10.02.2017 11:24
          -1

          Вообще-то в Observables источник данных понятия не имеет, кто на него подписан.
          Не в случае с .net

          https://msdn.microsoft.com/ru-ru/library/dd990377(v=vs.110).aspx


          1. mayorovp
            10.02.2017 11:29

            И что вы хотели этим сказать? Да, разумеется, поставщик имеет ссылки на всех подписчиков. Но он не может отличать их друг от друга. Точно так же как и в делегатах. Отличие-то в чем?


            1. MoreBeauty
              10.02.2017 17:55
              -1

              Может. Точнее имеет возможность. Это уже вопрос реализации. У IObserver нет метода Subscribe. А это значит что способ подписки остается на усмотрение программиста.


              1. lair
                10.02.2017 17:59
                +1

                У IObserver нет метода Subscribe. А это значит что способ подписки остается на усмотрение программиста.

                … способ подписки в Rx вполне известен: observable.Subscribe(observer). Никакого "усмотрения программиста".


                Но какое это имеет отношение к тому, знает ли источник данных о том, кто на него подписан?


          1. lair
            10.02.2017 11:33
            +1

            … и чем это отличается от обычных событий? Тем, что делегатов три, а не один?


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


            Вот, значит, мой код (который источник данных):


                private Subject<string> _messageReceived = new Subject<string>();
            
                public IObservable<string> MessageReceived => _messageReceived.AsObservable();
            
                public void ReceiveMessage(string message)
                {
                    _messageReceived.OnNext(message);
                }

            А вот потребитель:


            messager.MessageReceived.Where(s => s.Length > threshold).Throttle(..)...

            И много у источника данных возможностей узнать, что с сообщением происходит?


            1. MoreBeauty
              10.02.2017 17:56

              Зачем вы привели пример, в котором не дали эту возможность? :)


              1. lair
                10.02.2017 18:00

                Затем, что это один из стандартных способ работы с observables в .net. И, что характерно, все остальные тоже устроены так, что источник данных (observable) отвязан от получателя (observer) и знает о нем только и исключительно интерфейс.


                Так что ну да, попробуйте привести другой пример, где observable будет как-то различать своих подписчиков.


      1. mayorovp
        10.02.2017 11:00

        Вы путаете шаблоны "наблюдатель" и "публикация/подписка".


        1. MoreBeauty
          10.02.2017 11:25
          -2

          Вы путаете шаблоны «наблюдатель» и «публикация/подписка»
          Я как раз их не путаю


  1. MonkAlex
    10.02.2017 11:53
    +1

    А зачем по потоку на события? Обычно обработка события в одном потоке — наоборот ожидаемое поведение.
    Единственное логичное исключение — UI. Но тогда всегда можно явно запустить событие в диспатчере от UI потока.


    1. MoreBeauty
      10.02.2017 12:48

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


      1. lair
        10.02.2017 12:54

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

        Вообще-то, на этот вопрос нет формально верного ответа. События выполняются в том потоке, в котором вызывающая сторона сочла нужным их вызвать. Или вы спрашиваете, в каком потоке выполняется делегат во время вызова? Ну так это, в общем-то, очевидно: при вызове Invoke — в вызывающем потоке. При вызове BeginInvoke… на этот вопрос и вы ответа не дали пока.


        1. MoreBeauty
          10.02.2017 13:14

          Вообще-то, на этот вопрос нет формально верного ответа. События выполняются в том потоке, в котором вызывающая сторона сочла нужным их вызвать.

          В статье сказано:
          В общем обработчики события в примере выше выполняются в потоке, порожденном в методе DoWork(). То есть при генерации события, поток, который его сгенерировал таким образом, дожидается выполнения всех обработчиков.

          То есть я указал на приведенный мною пример. Я не стал обобщать, потому что именно различные реализации этого примера я чаще всего вижу в чужом коде. И вот это обобщение, которое вы привели — верное, но без разбора конкретного примера не всем будет понятно. Джуниоров намного больше, чем мидлов и сеньеров. И кода в общем объеме они выдают больше.
          Или вы спрашиваете, в каком потоке выполняется делегат во время вызова? Ну так это, в общем-то, очевидно
          К сожалению не для всех это очевидно.
          При вызове BeginInvoke… на этот вопрос и вы ответа не дали пока.
          Как не дал то? написал же что порождаются новые потоки.


          1. lair
            10.02.2017 13:16
            +1

            К сожалению не для всех это очевидно.

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


            Как не дал то? написал же что порождаются новые потоки.

            Вот только это неправильный ответ.


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


            1. MoreBeauty
              10.02.2017 13:29

              Знаете, людям, которым неочевидно, в каком потоке вызывается делегат, надо идти обратно учиться. Простите за прямоту.
              Согласен. Нас вот в универе не учили работе с потоками. Выходит, приходится заниматься самообразованием. Для этого есть курсы, книги и подобные статьи.

              Вот только это неправильный ответ.

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

              Почему не правильный то?

              Количество потоков будет равно Количеству подписчиков умноженному на количество вызовов. В случае с вашими цифрами их будет 400.

              Но к чему вообще этот вопрос?


              1. lair
                10.02.2017 13:38
                +1

                Нас вот в универе не учили работе с потоками.

                Так это не вопрос работы с потоками, это вопрос работы с делегатами. И это описано в любой книжке про C# и/или CLR.


                Почему не правильный то?

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


                Количество потоков будет равно Количеству подписчиков умноженному на количество вызовов. В случае с вашими цифрами их будет 400.

                … и вы серьезно считаете, что создавать 200 потоков во время вызова события — это нормально? То, что в этот момент вы съели (при настройках по умолчанию) 200мб памяти, вас не смущает?


                Но, к счастью, вы не правы. Будет создано не больше потоков, чем задано в тредпуле. А если приложение давно используется, и тредпул уже разогрет — то ни одного.


                Но к чему вообще этот вопрос?

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


                1. MoreBeauty
                  10.02.2017 13:46

                  Так это не вопрос работы с потоками, это вопрос работы с делегатами. И это описано в любой книжке про C# и/или CLR.

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

                  … и вы серьезно считаете, что создавать 200 потоков во время вызова события — это нормально? То, что в этот момент вы съели (при настройках по умолчанию) 200мб памяти, вас не смущает?
                  Нет я так не считаю, я уже написал об этом раз 10.

                  К тому, что вы сам, похоже, не понимаете, как работает предлагаемое вами решение.
                  В работе с потоками я прикрылся за абстракциями. Мне не важно, как отработает пул. Мне важны 2 вещи:
                  1. Обработчики будут выполняться параллельно (да, на самом деле все не совсем так и одновременных потоков будет по количеству ядер, свободной памяти и тд, но я не раскрываю данную абстракцию в этой статье. Именно поэтому я и говорю, что для вашего примера потоков будет 400, ведь я потребовал от системы генерацию 400 потоков)
                  2. Породитель событий не будет дожидаться их выполнение и продолжит свою работу.

                  Зачем вы пытаетесь мне сказать, что я неправильно работаю с потоками, если я в начале статьи сказал, что я неправильно с ними работаю и статья не о них?


                  1. lair
                    10.02.2017 13:56

                    Но статья не про потоки или делегаты!

                    А про что же она, если события — это делегаты, и вы рассказываете, как устроены/работают делегаты, и спрашиваете, на каком потоке делегат выполнится?


                    Вы лезете в дебри.

                    Это не дебри, это самые что ни на есть азы.


                    Возьмите 200 ядер и ваши потоки выполнятся в раз.

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


                    Нет я так не считаю, я уже написал об этом раз 10.

                    Тогда зачем вы предлагаете такое решение?


                    В работе с потоками я прикрылся за абстракциями.

                    Нет, вы написали "BeginInvoke порождает новый поток". Это не абстракция, это конкретное — и ложное — утверждение.


                    Мне не важно, как отработает пул.

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


                    Зачем вы пытаетесь мне сказать, что я неправильно работаю с потоками, если я в начале статьи сказал, что я неправильно с ними работаю и статья не о них?

                    Затем, чтобы (а) вы не понимаете, что неправильно работете с потоками и (б) я не хочу видеть пример неправильной работы с потоками.


                    Именно поэтому я и говорю, что для вашего примера потоков будет 400, ведь я потребовал от системы генерацию 400 потоков

                    Это как раз пример того, что вы не понимаете происходящего. Сделав 400 BeginInvoke вы не потребовали от системы генерацию 400 потоков.


                    1. Lofer
                      10.02.2017 14:10

                      Я бы рекомендовал ознакомиться вот с этим материалом.
                      MCTS Self-Paced Training Kit (Exam 70-536)
                      Если мне не изменяет память там было достаточное описание, для основ. (Chapter 7
                      Threading, Lesson 3: The Asynchronous Programming Model )
                      После без проблем «поверх ляжет» await & async


                    1. MoreBeauty
                      10.02.2017 18:14
                      -2

                      Да вы сами похоже не понимаете что происходит.

                      Это не дебри, это самые что ни на есть азы.

                      Так разберитесь с ними сами прежде всего.

                      Нет, вы написали «BeginInvoke порождает новый поток». Это не абстракция, это конкретное — и ложное — утверждение.

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

                      Затем, чтобы (а) вы не понимаете, что неправильно работете с потоками и (б) я не хочу видеть пример неправильной работы с потоками.

                      Это как раз пример того, что вы не понимаете происходящего. Сделав 400 BeginInvoke вы не потребовали от системы генерацию 400 потоков.

                      Класс Thread в принципе не является потоком. Это обертка. Абстракция.
                      Говоря поток, я имею ввиду Thread. Говоря «порождает» поток, я имею ввиду создает Thread и дает ему команду «Пуск». Так вот, одновременно (почти) создаются 400 экземпляров Thread. Когда они будут выполняться, генератору событий не важно. Ему даже не важно, будут ли они вообще выполняться, или лягут с ошибкой. Обработка событий не входит в его компетенцию. Вы со своими заумными фразами про потоки забыли про принципы ООП!
                      Теперь что касается этих 400 потоков. Да, я просто кинул их бездумно в пул. Ну так мне не важно, как, когда и в каком порядке они будут выполняться и программа больше ничего не делает. Я не могу в этом случае рассмотреть каждый случай отдельно, поэтому ПРАВИЛЬНОГО решения тут нет и быть не может! Для случая, когда потребуется соблюдать порядок, я создам очередь из задач (или шелдер). Для случая, когда выполнение других потоков будет важнее, я укажу приоритет для этих событий ниже. Зато, если я не вынесу порождение событий в отдельный от воркера поток, мне придется снижать приоритет всего воркера по отношению к другим потокам. Но это уже тема не для джуниоров и я не стал включать это все в статью!
                      А вы тут обсуждаете абстрактного коня в вакууме, и при этом ругаетесь на моего коня. Мой конь круче!


                      1. lair
                        10.02.2017 18:26
                        +2

                        Говоря «порождает» поток, я имею ввиду создает Thread и дает ему команду «Пуск». Так вот, одновременно (почти) создаются 400 экземпляров Thread.

                        Круто, а какие-то основания для этих утверждений у вас есть? Потому что "создать экземпляр класса Thread и сказать ему Start" — это и есть создание нового потока.


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

                        Это если у вас fire-and-forget, что далеко не для всех событий верно. А вы это почему-то считаете поведением по умолчанию.


                        Вы со своими заумными фразами про потоки забыли про принципы ООП!

                        … принципы ООП тут ни при чем.


                        Теперь что касается этих 400 потоков. Да, я просто кинул их бездумно в пул.

                        Да нет же. Нельзя кинуть потоки в пул.


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

                        Отнюдь. Я обсуждаю ваш конкретный код.


                        1. MoreBeauty
                          11.02.2017 06:56
                          -3

                          Вот я и говорю что вы сами не понимаете что говорите.

                          Круто, а какие-то основания для этих утверждений у вас есть? Потому что «создать экземпляр класса Thread и сказать ему Start» — это и есть создание нового потока.
                          Поток при этом не создается. Потоками пул управляет, а не пользователь. После команды старт происходит запрос на запуск потока (или как то так). Я могу стартануть 400 экземпляров Thread, но реально работать будут при этом только 4 из них (на моем железе и среде).
                          Это если у вас fire-and-forget, что далеко не для всех событий верно. А вы это почему-то считаете поведением по умолчанию.

                          Ничего не слышал про fire-and-forget для системы событий в .net. Гугл не помог. Подскажите где почитать, потому что в данный момент мне это кажется плохой архитектурой а не особым подходом.
                          Да нет же. Нельзя кинуть потоки в пул.
                          Беда с терминологие, походу… я создал 400 экземпляров Thread и сказал им старт.

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


                          1. mayorovp
                            11.02.2017 07:51

                            Прочитайте хотя бы документацию!


                            https://msdn.microsoft.com/ru-ru/library/system.threading.thread(v=vs.110).aspx


                            Класс Thread
                            Создает и контролирует поток, задает приоритет и возвращает статус.


                          1. lair
                            11.02.2017 09:52

                            Поток при этом не создается.

                            Серьезно? Ссылку на доку вам ниже дали, вот вам еще из Рихтера цитаты.


                            Во-первых: "So today, a CLR thread is identical to a Windows thread".
                            Во-вторых: "To actually create the operating system thread and have it start executing the callback method, you must call Thread’s Start method, passing into it the object (state) that you want passed as the callback method’s argument."


                            Окей, в документацию вы не верите, давайте эксперимент устроим.


                            Код
                                    {
                                        var info = new ConcurrentBag<ThreadInfo>();
                            
                                        const int howMany = 400;
                            
                                        Console.WriteLine("For Thread pool:");
                                        var handles = Enumerable
                                            .Range(1, howMany)
                                            .Select(_ =>
                                            {
                                                var manualResetEventSlim = new ManualResetEventSlim();
                                                ThreadPool.QueueUserWorkItem(h =>
                                                {
                                                    info.Add(new ThreadInfo
                                                    {
                                                        IsThreadPool = Thread.CurrentThread.IsThreadPoolThread,
                                                        ThreadId = Thread.CurrentThread.ManagedThreadId
                                                    });
                                                    Thread.Sleep(500);
                                                    ((ManualResetEventSlim)h).Set();
                                                }, manualResetEventSlim);
                                                return manualResetEventSlim.WaitHandle;
                                            }).ToArray();
                            
                                        var threads = Process.GetCurrentProcess().Threads.Count;
                                        Console.WriteLine($"Threads in process: {threads}");
                            
                                        //не влезаем в максимально допустимое число в WaitAll
                                        foreach (var handle in handles)
                                            handle.WaitOne();
                            
                                        Console.WriteLine($"Invocations: {info.Count}");
                                        Console.WriteLine($"Different threads ids: {info.Select(i => i.ThreadId).Distinct().Count()}");
                                        Console.WriteLine($"Count of thread pool threads: {info.Count(i => i.IsThreadPool)}");
                            
                                        var threadPoolIds = new HashSet<int>(info.Select(i => i.ThreadId));
                            
                                        Console.WriteLine();
                                        Console.WriteLine("For Thread:");
                                        info = new ConcurrentBag<ThreadInfo>();
                                        handles = Enumerable
                                            .Range(1, howMany)
                                            .Select(_ =>
                                            {
                                                var manualResetEventSlim = new ManualResetEventSlim();
                                                var thread = new Thread(h =>
                                                {
                                                    info.Add(new ThreadInfo
                                                    {
                                                        IsThreadPool = Thread.CurrentThread.IsThreadPoolThread,
                                                        ThreadId = Thread.CurrentThread.ManagedThreadId
                                                    });
                                                    Thread.Sleep(500);
                                                    ((ManualResetEventSlim)h).Set();
                                                });
                                                thread.Start(manualResetEventSlim);
                                                return manualResetEventSlim.WaitHandle;
                                            }).ToArray();
                            
                                        threads = Process.GetCurrentProcess().Threads.Count;
                                        Console.WriteLine($"Threads in process: {threads}");
                            
                                        //не влезаем в максимально допустимое число в WaitAll
                                        foreach (var handle in handles)
                                            handle.WaitOne();
                            
                                        Console.WriteLine($"Invocations: {info.Count}");
                                        Console.WriteLine($"Different threads ids: {info.Select(i => i.ThreadId).Distinct().Count()}");
                                        Console.WriteLine($"Matching to thread pool thread ids: {info.Select(i => i.ThreadId).Where(threadPoolIds.Contains).Distinct().Count()}");
                                        Console.WriteLine($"Count of thread pool threads: {info.Count(i => i.IsThreadPool)}");
                            
                                        Console.WriteLine("");
                                        Console.WriteLine("For BeginInvoke:");
                            
                                        info = new ConcurrentBag<ThreadInfo>();
                                        handles = Enumerable
                                            .Range(1, howMany)
                                            .Select(_ =>
                                            {
                                                Action d = () =>
                                                {
                                                    info.Add(new ThreadInfo
                                                    {
                                                        IsThreadPool = Thread.CurrentThread.IsThreadPoolThread,
                                                        ThreadId = Thread.CurrentThread.ManagedThreadId
                                                    });
                                                    Thread.Sleep(500);
                                                };
                                                return d.BeginInvoke(d.EndInvoke, null).AsyncWaitHandle;
                                            }).ToArray();
                            
                                        threads = Process.GetCurrentProcess().Threads.Count;
                                        Console.WriteLine($"Threads in process: {threads}");
                            
                                        //не влезаем в максимально допустимое число в WaitAll
                                        foreach (var handle in handles)
                                            handle.WaitOne();
                            
                                        Console.WriteLine($"Invocations: {info.Count}");
                                        Console.WriteLine($"Different threads ids: {info.Select(i => i.ThreadId).Distinct().Count()}");
                                        Console.WriteLine($"Matching to thread pool thread ids: {info.Select(i => i.ThreadId).Where(threadPoolIds.Contains).Distinct().Count()}");
                                        Console.WriteLine($"Count of thread pool threads: {info.Count(i => i.IsThreadPool)}");
                                    }


              1. Lofer
                10.02.2017 13:38

                Почему не правильный то?

                Полагаю, что нюанс в этом: Dispatcher Class

                Dispatcher.BeginInvoke Method (Delegate,?DispatcherPriority,?Object[])
                .NET Framework (current version)

                Executes the specified delegate asynchronously with the specified arguments, at the specified priority, on the thread that the Dispatcher was created on.
                Namespace: System.Windows.Threading
                Assembly: WindowsBase (in WindowsBase.dll)

                Нас вот в универе не учили работе с потоками.

                Это все есть в документации. Согласен что муторно, то иногда надо :)


                1. MoreBeauty
                  10.02.2017 18:25
                  -1

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


  1. Lofer
    10.02.2017 13:15

    Вроде как за последние лет 20 выработаны достаточно походов к решение многопоточного взаимодействия?
    Что-то из уже сделанного не подходит?
    Обычно на этапе дизайна уже ищутся ответы на вопросы:

    1. Как должен себя вести родительский поток, если упадет созданный поток ?
    2. Стратегия обработки ошибок ?
    3. Стратегия работы с общими ресурсами ?

    Исходя из этого уже и выбирают все events / messages / и т.д.

    Полагаю, что проблема тут:
    То есть генератору событий теперь до лампочки, кто, как и как долго будет обрабатывать его события.


    Нафига приложение без «TimeOut»? Выжрать все ресурсы в одно рыло один генератор? Так вроде бы вычислительные ресуры то общие (см п3 выше)

    А какую проблему то это все должно решить?


    1. MoreBeauty
      10.02.2017 13:23
      -2

      Да, да и еще раз да! Я устал отвечать на одни и те же замечания. Статья не об организации потоков, она просто рассказывает о том, в каких потоках выполняются события. Не все об этом знают. Это для джуниоров. Про организацию потоков я ничего не писал. Для этого одной статьи просто может не хватить из-за количества вариаций.


      1. Lofer
        10.02.2017 13:44
        +1

        в каких потоках выполняются события

        Поймите простую штуку: дьявол в деталях.
        События не могут «выполняться», события можно вызвать.
        А выполняется «Обработчик события».
        Это два разных элемента. Когда вы сможете их рассмотреть отдельно, все станет на свои места. Пока Вы будете их рассматривать «связно» вы не получите правильного ответа.
        Сам вопрос поставлен не очень корректно: как правильно молотком закручивать шуруп? :)
        Правильный ответ: возьмите отвертку.


        1. MoreBeauty
          10.02.2017 18:21

          Вот, проблемы с терминологией. Заработался, простите. Я то же самое объяснял оратору выше.


  1. lair
    10.02.2017 15:01

    ((EventHandler<T>)delegates[i]).BeginInvoke(sender, e, AsyncCallbackClass<T>.CallBack, null);
    
    //...
    
    static class AsyncCallbackClass<T2> where T2 : EventArgs
            {
                public static void CallBack(IAsyncResult ar)
                {
                    var tar = ar as AsyncResult;
                    if (tar != null)
                        ((EventHandler<T2>)tar.AsyncDelegate).EndInvoke(ar);
                }
            }

    Зачем так сложно, чем вам не угодил прямой вызов EndInvoke?


    foreach (EventHandler<EventArgs> handler in h.GetInvocationList())
    {
        handler.BeginInvoke(sender, e, handler.EndInvoke, null);
    }


    1. mayorovp
      10.02.2017 17:10

      Более того, его код еще и неверный. Никто не обязывает CLR возвращать какой-то особый публичный AsyncResult...


      1. lair
        10.02.2017 17:17

        … а если вернут не его, то мы молча пойдем дальше, так и не вызвав EndInvoke. Красота же.


    1. MoreBeauty
      10.02.2017 18:18
      -1

      Спасибо за заметку, сейчас исправлю


      1. lair
        10.02.2017 18:27
        +1

        public static void RaiseEventAsync<T>(EventHandler<T> h, object sender, T e) where T : EventArgs
        {
          if (h != null)
          {
            var delegates = h.GetInvocationList();
            for (var i = 0; i < delegates.Length; i++)
              ((EventHandler<T>)delegates[i]).BeginInvoke(sender, e, h.EndInvoke, null);
          }
        }

        Серьезно?


  1. Yarikz
    11.02.2017 06:56
    +1

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

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

    Более того, ваша реализация упирается в ограничения Thread Pool, и обработчики событий скорее всего выстроятся в очередь, в зависимости от загруженности пула; что еще хуже, пул может начать создавать новые потоки, которые неизбежно снизят производительность.


    1. MoreBeauty
      11.02.2017 06:58
      -1

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


      1. mayorovp
        11.02.2017 07:53
        +1

        Да кто такой этот Вася Пупкин, что вы его так боитесь — и одновременно о нем так заботитесь? И что он делает в вашей программе?


      1. Lofer
        11.02.2017 12:15

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

        Это как раз вас поставит в рамки :) Ваш генератор станет «колом», поскольу ничего не сгенерит, пока обработчик не вернет управление.
        Если вы разрабатываете библиотеку «для того парня», вы обязаны продумать API взаимодействия. В частности Вашей задачей будет предоставить и делегаты под обработку и декларацию событий и модель асинхронной работы (Event Based, Task Based и т.д).
        В общем случае не ваша забота, как будут обрабатываться ваши события и по какой модели. Даже если вы сделаете «просто один поток» то обработчики сами себе придумают как быстро вернуть управление генератору.
        Вопрос только в том — генератору нужен результат «обработчика или нет».


  1. dymanoid
    13.02.2017 23:05

    Думал, что эту тему уже давно все разжевали и усвоили, гуру почитали. Ан нет, баталии не на жизнь, а на смерть!