Comments 94
И в чем же преимущество EventHandler<>
над Action
или MyDelegate
?
Есть и еще способ — создать новый класс, унаследованный от Delegate или MulticastDelegate, но это уже для совсем специфичных случаев. Фактически, способ 2 это и делает за кулисами.
Как вы вообще это себе представляете?
error CS0644: 'ExampleHandler' cannot derive from special class 'MulticastDelegate'
Считайте это упрощением работы событий для частного случая. Но дело в том, что этот частный случай встречается чаще других при разработке.
При использовании Action<>
с одним параметром такой фокус делается столь же просто.
Если событие что-то возвращает — то у него, во-первых, не может быть более 1 подписчика — а во-вторых, нам в любом случае надо дождаться результата. В итоге никакой AsyncEventsHelper попросту не нужен.
Может быть несколько подписчиков, просто результут вернётся только из последнего
Неявно забытое возвращаемое значение — это ошибка. Если его старались-возвращали — значит, оно кому-то было нужно.
Дженерик же.
[Serializable]
[System.Runtime.InteropServices.ComVisible(true)]
public class EventArgs {
public static readonly EventArgs Empty = new EventArgs();
public EventArgs()
{
}
}
Да, действительно, написать свой собственный аналог этого класса — так сложно!
Вы понимаете, что у приведенного вами же класса MyEventArgs
можно "оторвать" базовый тип — и от этого ничего не изменится (после перехода на Action)?
Вообще-то, это был мой вопрос.
А зачем? Зачем использовать EventHandler-то?
Чтобы не писать EventHandler вокруг одного строкового значения.
Ну хорошо, а если надо больше аргументов события?
Берется любой класс, в чем проблема-то?
Ну опишите универсальный способ для многопоточной обработки событий.
Задача неполна, поэтому и решения ей быть не может. Если вам надо fire-and-forget — берете очередь и пишете в нее, все остальное вас не волнует.
для Action
для Actionдля Action<T, T>
и тд до 18 аргументов
Задача вполне конкретна. у меня есть генератор событий, который принимает хандлер типа EventHandler и с ним работает. А в случае с Action<> у вас будет 19 таких генераторов по количеству разновидностей Action.
Неа. Я просто ограничу "события" Action<T>
и все.
(собственно, в Rx так и сделано)
В смысле "будет ли он ограничен"? Нет, не будет. Зачем?
Ну так от события зависит. Если у меня конкретное событие принимает строку, будет Action<string>
, если http-запрос — Action<HttpRequestMessage>
, ну и так далее.
Action<object, MyClass>
Action — это тоже делегат. и казалось бы, почему бы тогда не использовать его?
но вы ведь не обязательно будете порождать собственные события. Что, если вы захотите породить событие, описанное где то в дебрях фрэймворка? А вот они то как раз являются чем то на подобие
SomeNetEventHandler: EventHandler<SomeNeteventArgs>
и тут мой универсальный метод будет кстати. Я ответил на ваш вопрос?
Да, но экземпляров, порождающих событие может быть несколько и обработчику может захотеться узнать, какой экземпляр породил событие.
Обработчику может хотеться что угодно, набор предоставляемых данных определяется их источником.
Action<object, MyClass>
Достаточно Action<T>
, где T
определяется каждым событием, либо Action<TSource,TArgs>
, если я хочу передавать источник.
Вы, кстати, не представляете себе, как меня достал object sender
в стандартных событиях.
Что, если вы захотите породить событие, описанное где то в дебрях фрэймворка?
Напишу адаптер: (Action<T>) (T args) => handler(this, args)
.
Другое дело, что не захочу.
Я ответил на ваш вопрос?
Эээ, простите, какой вопрос?
Обработчику может хотеться что угодно, набор предоставляемых данных определяется их источникомСамо собой, но можно вообще не дать никаких данных тогда. Сказать что чего то изменилось, пусть обработчик сам выясняет что именно. :)
Достаточно Action<T>, где T определяется каждым событием, либо Action<TSource,TArgs>, если я хочу передавать источник.
Вы, кстати, не представляете себе, как меня достал object sender в стандартных событиях.
Почему же не представляю? Представляю. Но иногда за универсальность приходится платить.
Иногда один обработчик может использоваться для аналогичных событий разных типов. И если породитель события нам в данном случае не интересен, то object sender мы оставим без внимания, а вместо этого обработаем аргументы события.
Согласен, чаще всего универсальность в этом излишня.
Сейчас уберу из статьи гневные комментики по поводу Action
Вы же вкурсе что на каждый Begin<Method>
должен быть свой End<Method>
? ;)
Если я чего то не знаю, расскажите, я сам не так давно начал копать систему работы событий и буду рад любым замечаниям.
Вообще-то, метод EndInvoke
еще и освобождает ресурсы!
Если вам не нужен результат — то существует очень простое решение:
var d = (EventHandler<T>)delegates[i];
d.BeginInvoke(sender, e, d.EndInvoke, null);
А пропускать вызов End-метода не следует.
Для начала можно почитать документацию: 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
не обязательно, но об этом тоже явно написано в документации.
Я не знаю, кто и как будет обрабатывать события, порожденные написанным мной классом, но мне не очень то хочется, чтобы эти обработчики могли повесить работу моего класса. Поэтому я буду использовать метод BeginInvoke вместо Invoke.
Круто, а теперь ответьте на свой же вопрос: в каком потоке выполняется событие?
Вот, значит, кто-то подписал на ваше событие две сотни обработчиков. Сколько будет потоков? Откуда они возьмутся? А откуда они возьмутся при втором вызове того же события?
Колбэк в данном примере нам не нужен.
Агу. А что будет, если в обработчике возникнет ошибка (exception кто-то бросит)?
Круто, а теперь ответьте на свой же вопрос: в каком потоке выполняется событие?
Тут из-за нечеткой терминологии могут возникнуть непонятки у нас. Событие не выполняется, оно генерируется. Ну или как то так. А выполняются обработчики. Так вот в моем примере каждый обработчик выполняется в отдельном потоке.
Вот, значит, кто-то подписал на ваше событие две сотни обработчиков. Сколько будет потоков? Откуда они возьмутся? А откуда они возьмутся при втором вызове того же события?Я на старте еще написал, что речь тут не про потоки, а про события в контексте потоков. Я тут не занимался разруливанием потоков. Если вам необходимо рулить пулом, шелдером или еще чем, поищите информацию сами. Я лишь дал идею.
Агу. А что будет, если в обработчике возникнет ошибка (exception кто-то бросит)?Во-первых, в моем примере генератора событий вообще не волнует как это событие будет обработано. поэтому, если возникнет ошибка, то обрабатывать ее надо в методе-обработчике. В случае моего примера это
static void Raiser_CounterChanged(object sender, EventRaiserCounterChangedEventArgs e)
{
Console.WriteLine(string.Format("OldValue: {0}; NewValue: {1}", e.OldValue, e.NewValue));
}
Так вот в моем примере каждый обработчик выполняется в отдельном потоке.
Так в каком же?
Я на старте еще написал, что речь тут не про потоки, а про события в контексте потоков. Я тут не занимался разруливанием потоков. Если вам необходимо рулить пулом, шелдером или еще чем, поищите информацию сами. Я лишь дал идею.
Так вот, без знания ответов на эти вопросы ваша идея опасна.
Во-первых, в моем примере генератора событий вообще не волнует как это событие будет обработано. поэтому, если возникнет ошибка, то обрабатывать ее надо в методе-обработчике.
Так что же будет, если обработчик не обработает ошибку, и она из него вылетит?
Так в каком же?В новом
Так вот, без знания ответов на эти вопросы ваша идея опасна.Идея тут не может быть опасна. Опасна реализация. Но я же предупредил, что все примеры чисто тестовые. Я тут рассматриваю абстрактного коня в вакууме. Вот если бы я написал библиотеку и выложил на гитхаб, то данное замечание было бы уместно.
Так что же будет, если обработчик не обработает ошибку, и она из него вылетит?Тут все зависит от того, пытается ли обработчик лезть в другие потоки. Если он тихонечко работает в своем конткесте, то с больше долей вероятности он просто упадет и никому об этом не расскажет :)
Если вам все таки хочется об этом узнать, оборачивайте операции обработчика в try catch
В новом
Откуда он взялся? Какой у него оверхед?
Есть такое правило большого пальца, что постоянное создание новых потоков — это большое зло.
Идея тут не может быть опасна.
Может, может (я даже не буду приводить очевидные примеры). В вашем случае идея выглядит как "а давайте запустим все обработчики каждый в своем потоке". Первый же вопрос к это идее — это что делать, если обработчиков очень много.
Тут все зависит от того, пытается ли обработчик лезть в другие потоки.
Почему? Какая разница?
Если он тихонечко работает в своем конткесте, то с больше долей вероятности он просто упадет и никому об этом не расскажет
Так с большой долей вероятности или гарантированно? А не может так случиться, что он упадет и погребет все приложение?
Есть такое правило большого пальца, что постоянное создание новых потоков — это большое зло.Я с вами согласен, но к теме данной статьи это не относится
Может, может (я даже не буду приводить очевидные примеры). В вашем случае идея выглядит как «а давайте запустим все обработчики каждый в своем потоке». Первый же вопрос к это идее — это что делать, если обработчиков очень много.я не предлагал запускать в отдельных потоках, я предложил запустить в потоке отличном от потока работы генератора.
Почему? Какая разница?Потому что это не обсервер
Так с большой долей вероятности или гарантированно? А не может так случиться, что он упадет и погребет все приложение?Не имеет значение… обработка ошибок обработчика событий — дело обработчика событий а не их генератора.
Относится напрямую, потому что продвигаемый в статье подход — давайте создавать новый поток на каждый обработчик при каждом вызове события.
Прочитав данную статью, человек, который никогда не задумывался о том, где выполняются обработчики событий, должен это понять.
Для этого достаточно двух фраз: "Обработчики событий — это делегаты. Делегаты выполняются там, откуда их вызвали".
Но нет, вы зачем-то предлагаете сценарий, когда вам надо, чтобы обработчики вызывались не в том же потоке, что и порождающий тред, и предлагаете для этого неправильное решение.
BeginInvoke
на делегате тоже использует поток пула, нет никакого отличия от ThreadPool.QueueWorkItem
Запоминание SynchronizationContext
в событиях — отдельная проблема, решение которой больше этого поста раза в два :)
Использовать паттерн «наблюдатель», на мой взгляд, имеет смысл, когда генератору событий нужно знать, кто ожидает оповещений и/или выбирать, кому отправить данное оповещение, а кому нет.
Вообще-то в Observables источник данных понятия не имеет, кто на него подписан.
Вообще-то в Observables источник данных понятия не имеет, кто на него подписан.Не в случае с .net
https://msdn.microsoft.com/ru-ru/library/dd990377(v=vs.110).aspx
И что вы хотели этим сказать? Да, разумеется, поставщик имеет ссылки на всех подписчиков. Но он не может отличать их друг от друга. Точно так же как и в делегатах. Отличие-то в чем?
У IObserver нет метода Subscribe. А это значит что способ подписки остается на усмотрение программиста.
… способ подписки в Rx вполне известен: observable.Subscribe(observer)
. Никакого "усмотрения программиста".
Но какое это имеет отношение к тому, знает ли источник данных о том, кто на него подписан?
… и чем это отличается от обычных событий? Тем, что делегатов три, а не один?
(Не говоря уже о том, что типовой случай работы с 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(..)...
И много у источника данных возможностей узнать, что с сообщением происходит?
Затем, что это один из стандартных способ работы с observables в .net. И, что характерно, все остальные тоже устроены так, что источник данных (observable) отвязан от получателя (observer) и знает о нем только и исключительно интерфейс.
Так что ну да, попробуйте привести другой пример, где observable будет как-то различать своих подписчиков.
Вы путаете шаблоны "наблюдатель" и "публикация/подписка".
Единственное логичное исключение — UI. Но тогда всегда можно явно запустить событие в диспатчере от UI потока.
Я же написал, статья не про работу с потоками, а про то, в контексте каких потоков выполняются события.
Вообще-то, на этот вопрос нет формально верного ответа. События выполняются в том потоке, в котором вызывающая сторона сочла нужным их вызвать. Или вы спрашиваете, в каком потоке выполняется делегат во время вызова? Ну так это, в общем-то, очевидно: при вызове Invoke
— в вызывающем потоке. При вызове BeginInvoke
… на этот вопрос и вы ответа не дали пока.
Вообще-то, на этот вопрос нет формально верного ответа. События выполняются в том потоке, в котором вызывающая сторона сочла нужным их вызвать.
В статье сказано:
В общем обработчики события в примере выше выполняются в потоке, порожденном в методе DoWork(). То есть при генерации события, поток, который его сгенерировал таким образом, дожидается выполнения всех обработчиков.
То есть я указал на приведенный мною пример. Я не стал обобщать, потому что именно различные реализации этого примера я чаще всего вижу в чужом коде. И вот это обобщение, которое вы привели — верное, но без разбора конкретного примера не всем будет понятно. Джуниоров намного больше, чем мидлов и сеньеров. И кода в общем объеме они выдают больше.
Или вы спрашиваете, в каком потоке выполняется делегат во время вызова? Ну так это, в общем-то, очевидноК сожалению не для всех это очевидно.
При вызове BeginInvoke… на этот вопрос и вы ответа не дали пока.Как не дал то? написал же что порождаются новые потоки.
К сожалению не для всех это очевидно.
Знаете, людям, которым неочевидно, в каком потоке вызывается делегат, надо идти обратно учиться. Простите за прямоту.
Как не дал то? написал же что порождаются новые потоки.
Вот только это неправильный ответ.
Я повторю свой пример: вот у нас есть событие, на него подписано двести обработчиков. Мы дважды вызвали это событие. Сколько потоков будет создано?
Знаете, людям, которым неочевидно, в каком потоке вызывается делегат, надо идти обратно учиться. Простите за прямоту.Согласен. Нас вот в универе не учили работе с потоками. Выходит, приходится заниматься самообразованием. Для этого есть курсы, книги и подобные статьи.
Вот только это неправильный ответ.
Я повторю свой пример: вот у нас есть событие, на него подписано двести обработчиков. Мы дважды вызвали это событие. Сколько потоков будет создано?
Почему не правильный то?
Количество потоков будет равно Количеству подписчиков умноженному на количество вызовов. В случае с вашими цифрами их будет 400.
Но к чему вообще этот вопрос?
Нас вот в универе не учили работе с потоками.
Так это не вопрос работы с потоками, это вопрос работы с делегатами. И это описано в любой книжке про C# и/или CLR.
Почему не правильный то?
Потому что BeginInvoke
на делегате выбрасывает делегат в тредпул, а не в новый поток. Со всеми занятными вытекающими.
Количество потоков будет равно Количеству подписчиков умноженному на количество вызовов. В случае с вашими цифрами их будет 400.
… и вы серьезно считаете, что создавать 200 потоков во время вызова события — это нормально? То, что в этот момент вы съели (при настройках по умолчанию) 200мб памяти, вас не смущает?
Но, к счастью, вы не правы. Будет создано не больше потоков, чем задано в тредпуле. А если приложение давно используется, и тредпул уже разогрет — то ни одного.
Но к чему вообще этот вопрос?
К тому, что вы сам, похоже, не понимаете, как работает предлагаемое вами решение.
Так это не вопрос работы с потоками, это вопрос работы с делегатами. И это описано в любой книжке про C# и/или CLR.
Но статья не про потоки или делегаты!
Потому что BeginInvoke на делегате выбрасывает делегат в тредпул, а не в новый поток. Со всеми занятными вытекающими.Вы лезете в дебри. Возьмите 200 ядер и ваши потоки выполнятся в раз.
… и вы серьезно считаете, что создавать 200 потоков во время вызова события — это нормально? То, что в этот момент вы съели (при настройках по умолчанию) 200мб памяти, вас не смущает?Нет я так не считаю, я уже написал об этом раз 10.
К тому, что вы сам, похоже, не понимаете, как работает предлагаемое вами решение.В работе с потоками я прикрылся за абстракциями. Мне не важно, как отработает пул. Мне важны 2 вещи:
1. Обработчики будут выполняться параллельно (да, на самом деле все не совсем так и одновременных потоков будет по количеству ядер, свободной памяти и тд, но я не раскрываю данную абстракцию в этой статье. Именно поэтому я и говорю, что для вашего примера потоков будет 400, ведь я потребовал от системы генерацию 400 потоков)
2. Породитель событий не будет дожидаться их выполнение и продолжит свою работу.
Зачем вы пытаетесь мне сказать, что я неправильно работаю с потоками, если я в начале статьи сказал, что я неправильно с ними работаю и статья не о них?
Но статья не про потоки или делегаты!
А про что же она, если события — это делегаты, и вы рассказываете, как устроены/работают делегаты, и спрашиваете, на каком потоке делегат выполнится?
Вы лезете в дебри.
Это не дебри, это самые что ни на есть азы.
Возьмите 200 ядер и ваши потоки выполнятся в раз.
Во-первых, не обязательно. Во-вторых, все равно не понятно, сколько их будет создано.
Нет я так не считаю, я уже написал об этом раз 10.
Тогда зачем вы предлагаете такое решение?
В работе с потоками я прикрылся за абстракциями.
Нет, вы написали "BeginInvoke порождает новый поток". Это не абстракция, это конкретное — и ложное — утверждение.
Мне не важно, как отработает пул.
… вот только в статье нет ни слова о том, что делегат будет выполнен на пуле. Более того, явно написано, что будет порожден новый поток.
Зачем вы пытаетесь мне сказать, что я неправильно работаю с потоками, если я в начале статьи сказал, что я неправильно с ними работаю и статья не о них?
Затем, чтобы (а) вы не понимаете, что неправильно работете с потоками и (б) я не хочу видеть пример неправильной работы с потоками.
Именно поэтому я и говорю, что для вашего примера потоков будет 400, ведь я потребовал от системы генерацию 400 потоков
Это как раз пример того, что вы не понимаете происходящего. Сделав 400 BeginInvoke
вы не потребовали от системы генерацию 400 потоков.
MCTS Self-Paced Training Kit (Exam 70-536)
Если мне не изменяет память там было достаточное описание, для основ. (Chapter 7
Threading, Lesson 3: The Asynchronous Programming Model )
После без проблем «поверх ляжет» await & async
Это не дебри, это самые что ни на есть азы.
Так разберитесь с ними сами прежде всего.
Нет, вы написали «BeginInvoke порождает новый поток». Это не абстракция, это конкретное — и ложное — утверждение.
… вот только в статье нет ни слова о том, что делегат будет выполнен на пуле. Более того, явно написано, что будет порожден новый поток.
Затем, чтобы (а) вы не понимаете, что неправильно работете с потоками и (б) я не хочу видеть пример неправильной работы с потоками.
Это как раз пример того, что вы не понимаете происходящего. Сделав 400 BeginInvoke вы не потребовали от системы генерацию 400 потоков.
Класс Thread в принципе не является потоком. Это обертка. Абстракция.
Говоря поток, я имею ввиду Thread. Говоря «порождает» поток, я имею ввиду создает Thread и дает ему команду «Пуск». Так вот, одновременно (почти) создаются 400 экземпляров Thread. Когда они будут выполняться, генератору событий не важно. Ему даже не важно, будут ли они вообще выполняться, или лягут с ошибкой. Обработка событий не входит в его компетенцию. Вы со своими заумными фразами про потоки забыли про принципы ООП!
Теперь что касается этих 400 потоков. Да, я просто кинул их бездумно в пул. Ну так мне не важно, как, когда и в каком порядке они будут выполняться и программа больше ничего не делает. Я не могу в этом случае рассмотреть каждый случай отдельно, поэтому ПРАВИЛЬНОГО решения тут нет и быть не может! Для случая, когда потребуется соблюдать порядок, я создам очередь из задач (или шелдер). Для случая, когда выполнение других потоков будет важнее, я укажу приоритет для этих событий ниже. Зато, если я не вынесу порождение событий в отдельный от воркера поток, мне придется снижать приоритет всего воркера по отношению к другим потокам. Но это уже тема не для джуниоров и я не стал включать это все в статью!
А вы тут обсуждаете абстрактного коня в вакууме, и при этом ругаетесь на моего коня. Мой конь круче!
Говоря «порождает» поток, я имею ввиду создает Thread и дает ему команду «Пуск». Так вот, одновременно (почти) создаются 400 экземпляров Thread.
Круто, а какие-то основания для этих утверждений у вас есть? Потому что "создать экземпляр класса Thread
и сказать ему Start
" — это и есть создание нового потока.
Когда они будут выполняться, генератору событий не важно. Ему даже не важно, будут ли они вообще выполняться, или лягут с ошибкой. Обработка событий не входит в его компетенцию.
Это если у вас fire-and-forget, что далеко не для всех событий верно. А вы это почему-то считаете поведением по умолчанию.
Вы со своими заумными фразами про потоки забыли про принципы ООП!
… принципы ООП тут ни при чем.
Теперь что касается этих 400 потоков. Да, я просто кинул их бездумно в пул.
Да нет же. Нельзя кинуть потоки в пул.
А вы тут обсуждаете абстрактного коня в вакууме, и при этом ругаетесь на моего коня.
Отнюдь. Я обсуждаю ваш конкретный код.
Круто, а какие-то основания для этих утверждений у вас есть? Потому что «создать экземпляр класса Thread и сказать ему Start» — это и есть создание нового потока.Поток при этом не создается. Потоками пул управляет, а не пользователь. После команды старт происходит запрос на запуск потока (или как то так). Я могу стартануть 400 экземпляров Thread, но реально работать будут при этом только 4 из них (на моем железе и среде).
Это если у вас fire-and-forget, что далеко не для всех событий верно. А вы это почему-то считаете поведением по умолчанию.
Ничего не слышал про fire-and-forget для системы событий в .net. Гугл не помог. Подскажите где почитать, потому что в данный момент мне это кажется плохой архитектурой а не особым подходом.
Да нет же. Нельзя кинуть потоки в пул.Беда с терминологие, походу… я создал 400 экземпляров Thread и сказал им старт.
Отнюдь. Я обсуждаю ваш конкретный код.Пример с потоками абстрактный… мне, может, нужно было его на псевдо языке написать, чтобы это было понятно?
Прочитайте хотя бы документацию!
https://msdn.microsoft.com/ru-ru/library/system.threading.thread(v=vs.110).aspx
Класс Thread
Создает и контролирует поток, задает приоритет и возвращает статус.
Поток при этом не создается.
Серьезно? Ссылку на доку вам ниже дали, вот вам еще из Рихтера цитаты.
Во-первых: "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)}");
}
А вот результат:
For Thread pool:
Threads in process: 15
Invocations: 400
Different threads ids: 9
Count of thread pool threads: 400
For Thread:
Threads in process: 416
Invocations: 400
Different threads ids: 400
Matching to thread pool thread ids: 0
Count of thread pool threads: 0
For BeginInvoke:
Threads in process: 16
Invocations: 400
Different threads ids: 16
Matching to thread pool thread ids: 9
Count of thread pool threads: 400
Во-первых, хорошо видно, что "создать Thread и сказать ему Start" — это именно "создать новый поток": число тредов увеличилось ровно на запрошенное количество, число разных идентификаторов такое же.
Во-вторых, видно, что пул ведет себя иначе: число потоков выросло на 15, но для работы ему вообще хватило девяти.
В-третьих, видно, что BeginInvoke
ведет себя, как и ожидалось, идентично пулу (которым он, согласно источникам, и пользуется).
Потоками пул управляет, а не пользователь.
Это если вы используете ThreadPool
. А если вы явно создаете Thread
, то вы как раз явно управляете потоком. Он для этого и придуман.
После команды старт происходит запрос на запуск потока (или как то так)
Нет, после команды Start
создается новый поток, и ему отдается команда на запуск.
Я могу стартануть 400 экземпляров Thread, но реально работать будут при этом только 4 из них
А вот это уже вопрос того, сколько будет работать параллельно, а не сколько будет создано.
Ничего не слышал про fire-and-forget для системы событий в .net.
Fire-and-forget — это такой подход к организации событий, при котором отправителя не интересует ни было ли событие доставлено, ни было ли оно успешно обработано. Это ровно то, что делаете (делали) вы, не обрабатывая результат делегата.
Беда с терминологие, походу… я создал 400 экземпляров Thread и сказал им старт.
Нет. Вы 400 раз вызвали BeginInvoke
— который не создает Thread
.
Но если бы вы 400 раз сделали new Thread(d).Start()
— вы бы получили именно описанное мной поведение.
Почему не правильный то?
Полагаю, что нюанс в этом: 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)
Нас вот в универе не учили работе с потоками.
Это все есть в документации. Согласен что муторно, то иногда надо :)
Что-то из уже сделанного не подходит?
Обычно на этапе дизайна уже ищутся ответы на вопросы:
- Как должен себя вести родительский поток, если упадет созданный поток ?
- Стратегия обработки ошибок ?
- Стратегия работы с общими ресурсами ?
Исходя из этого уже и выбирают все events / messages / и т.д.
Полагаю, что проблема тут:
То есть генератору событий теперь до лампочки, кто, как и как долго будет обрабатывать его события.
Нафига приложение без «TimeOut»? Выжрать все ресурсы в
А какую проблему то это все должно решить?
в каких потоках выполняются события
Поймите простую штуку: дьявол в деталях.
События не могут «выполняться», события можно вызвать.
А выполняется «Обработчик события».
Это два разных элемента. Когда вы сможете их рассмотреть отдельно, все станет на свои места. Пока Вы будете их рассматривать «связно» вы не получите правильного ответа.
Сам вопрос поставлен не очень корректно: как правильно молотком закручивать шуруп? :)
Правильный ответ: возьмите отвертку.
((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);
}
Более того, его код еще и неверный. Никто не обязывает CLR возвращать какой-то особый публичный AsyncResult...
Я не знаю, кто и как будет обрабатывать события, порожденные написанным мной классом, но мне не очень то хочется, чтобы эти обработчики могли повесить работу моего класса. Поэтому я буду использовать метод BeginInvoke вместо Invoke.
Если вам не хочется, чтобы обработчик повесил работу класса, то напишите такой обработчик — это нетрудно. Вместо этого, вы заранее ставите пользователя в рамки вашей странной асинхронной модели, и решаете проблему не с той стороны. А что, если в обработчике я захочу запустить асинхронную операцию, для которой не нужен лишний поток?
Более того, ваша реализация упирается в ограничения Thread Pool, и обработчики событий скорее всего выстроятся в очередь, в зависимости от загруженности пула; что еще хуже, пул может начать создавать новые потоки, которые неизбежно снизят производительность.
Если вам не хочется, чтобы обработчик повесил работу класса, то напишите такой обработчик — это нетрудно.Генератор события написал я, а обработчик будет делать Вася Пупкин, который ничего не знает про логику работы моего генератора. поэтому, если обработчик будет работать в одном потоке с логикой генератора, это как раз и поставит разработчика в рамки.
Да кто такой этот Вася Пупкин, что вы его так боитесь — и одновременно о нем так заботитесь? И что он делает в вашей программе?
. поэтому, если обработчик будет работать в одном потоке с логикой генератора, это как раз и поставит разработчика в рамки.
Это как раз вас поставит в рамки :) Ваш генератор станет «колом», поскольу ничего не сгенерит, пока обработчик не вернет управление.
Если вы разрабатываете библиотеку «для того парня», вы обязаны продумать API взаимодействия. В частности Вашей задачей будет предоставить и делегаты под обработку и декларацию событий и модель асинхронной работы (Event Based, Task Based и т.д).
В общем случае не ваша забота, как будут обрабатываться ваши события и по какой модели. Даже если вы сделаете «просто один поток» то обработчики сами себе придумают как быстро вернуть управление генератору.
Вопрос только в том — генератору нужен результат «обработчика или нет».
События и потоки. Часть 1