Форум программистов, компьютерный форум, киберфорум
C#: ASP.NET Core
Войти
Регистрация
Восстановить пароль
Блоги Сообщество Поиск Заказать работу  
 
Рейтинг 4.67/6: Рейтинг темы: голосов - 6, средняя оценка - 4.67
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759

Blazor: асинхронные обработчики событий

26.12.2022, 23:41. Показов 1314. Ответов 8
Метки нет (Все метки)

Студворк — интернет-сервис помощи студентам
Пример отчасти синтетический, но на нём проблема повторяется и хочется разобраться как делать правильно.

В компоненте есть коллекция, на основании которой рендерится часть шаблона (список).

В шаблоне есть 'div', у которого есть два асинхронных обработчика событий - 'onkeydown' и 'onmouseover'. События спамятся в большом количестве.

Оба обработчика имеют схожую структуру:
C#
1
2
3
4
5
6
7
8
private async Task OnKeyDownHandler/OnMouseOverHandler(...)
{
    //удалить элемент из коллекции
    //await InvokeAsync(StateHasChanged);
    //ещё какие-то действия с await и без
    //добавить элемент в коллекцию
    //неявный StateHasChanged от движка blazor по факту завершения обрабочтика события
}
Т.е. на первом же 'await' управление возвращается в диспетчер blazor (ну, тот поток, который управляет жизненным циклом компонента) и он может вызывать новые обработчики или запускать перерендеринг компонента. Что он и делает при любом удобном случае.

Проблемный сценарий №1 - обработчики конфликтуют между собой на модификации коллекции. Повторяется стабильно. Обойти можно через явные блокировки или конкурентные коллекции. В т.ч. обработчик может конфликтовать сам с собой, если он ушёл в 'await' и не успел завершиться до того, как пришло следующее аналогичное событие.

Проблемный сценарий №2 - предполагаемый конфликт между блазоровским методом рендернига и обработчиками событий на той самой коллекции, т.е.:
1. Обрабочтик удалил элемент из коллекции.
2. Выполнил 'await InvokeAsync(StateHasChanged)' и вернул управление.
3. Блазор-движок запустил перерисовку компонента и начал обход коллекции через 'foreach'.
4. И пока он её обходит, любой из обработчиков параллельно выполнил удаление или добавление элемента коллекции.

Повторить почему-то не удаётся, хотя вроде как, должно падать. При этом весь код после 'StateHasChanged' в обоих обработчиках точно выполняется асинхронно, т.е. если там добавить тестовые 'await Task.Delay', то видно, что событие 'OnAfterRenderAsync' приходит независимо от того, на каком этапе выполнения находится обработчик.
0
Programming
Эксперт
39485 / 9562 / 3019
Регистрация: 12.04.2006
Сообщений: 41,671
Блог
26.12.2022, 23:41
Ответы с готовыми решениями:

Почему не работают обработчики событий для Blazor?
Имелось Веб-приложение ASP.NET Core 3.0 с использованием шаблона MVC. Пытаюсь в него добавить Blazor. Добавила тестовый контроллер и в...

Где живут обработчики событий ASP.NET?
Добрый день. Сразу скажу, что в ASP.Net абсолютный новичок, поэтому заранее прошу "понять и простить". Суть вопроса в...

обработчики событий
Нужна ваша помощь. Есть задание. Разработать новое приложение, в котором создать обработчики событий для формы (выбрать на вкладке...

8
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759
27.12.2022, 07:35  [ТС]
Более реалистичный пример:
1. Сложный компонент с десятком датагридов и кучей других полей для отображения.
2. У компонента, в силу любых причин, запустился очередной рендеринг (который занимает какое-то время).
3. И до того, как завершится п.2, откуда-то из нижних слоёв приложение в компонент пришло событие на актуализацию отображаемых данных. И обработчик события начал модифицировать состояние компонента. И в конце явным образом через 'StateHasChanged' запросил повторную перерисовку.

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

Но опять же - очевидно, что возможны конфликты при модификации коллекций.

И как это обходить?

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

Делать состояние компонента полностью иммутабельным и целиком подменять его при любых изменениях данных? Т.е. в начале шаблона сохранять актуальный экземпляр состояния компонента в локальную переменную и далее весь рендеринг выполнять по ней. В любых обработчиках, которые меняют состояние, каждый раз создавать новый экземпляр VM и в конце метода просто подменять ссылку на него. Выглядит куда более просто, однако, может порождать чрезмерное количество созданий/удалений экземпляров VM даже при изменении всего одного свойства.
0
 Аватар для sau
2773 / 2073 / 386
Регистрация: 22.07.2011
Сообщений: 7,820
27.12.2022, 16:10
kotelok, ну а чего Вы ожидали , тут как и везде . если у вас многопоточный доступ на запись к ресурсам , то нужно синхронизировать , вашем случае можно на уровне конкурентной коллекции. Т,е задача то типовая и к блазору особо отношения не имеющая , но даже и в случае и с блазором , если обновление компонента вызывается черт его знает из скольких потоков , притом там и порядок может быть произвольным , то без синхронизации получите что-то неопределенное на выходе.

Добавлено через 3 минуты
Цитата Сообщение от kotelok Посмотреть сообщение
1. Сложный компонент с десятком датагридов и кучей других полей для отображения.
т.е какие-то элементы могут независимо обновляться . но при этом вы пытаетесь перерисовать компонент полностью ?
- я бы эти элементы оформил в виде отдельных компанентиков для композиции основного,
тогда они смогут обновляться самостоятельно не затрагивая прочее по предназначенным для них событиям.
1
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759
27.12.2022, 23:01  [ТС]
Цитата Сообщение от sau Посмотреть сообщение
если обновление компонента вызывается черт его знает из скольких потоков , притом там и порядок может быть произвольным , то без синхронизации получите что-то неопределенное на выходе.
Как синхронизировать достук к ресурсу в разных потока более-менее понятно (хотя, там тоже есть нюансы именно со спамом DOM-событий). Вопрос больше как синхронизировать доступ к ресурсу в рамках процесса рендеринга блазор-шаблона. Прямо в код шаблона блокировки добавлять ? Делать имутабельное состояние? Писать свою надстройку на жизненным циклом компонента, подавлять всё через 'ShouldRender' и контролируемо запускать перерисовку в допустимые моменты? В общем, на словах это сложно, придётся экспериментировать.

Ну и как ни крути, но всё равно понадобится какой-то менеджер событий между источниками внешних событий и blazor-компонентом, просто чтобы прилетевшие за 500ms 100 событий модели не провоцировали 100 попыток перерендерить шаблон.

Добавлено через 2 минуты
Цитата Сообщение от sau Посмотреть сообщение
я бы эти элементы оформил в виде отдельных компанентиков для композиции основного,
тогда они смогут обновляться самостоятельно не затрагивая прочее по предназначенным для них событиям.
Да, это хорошая идея, иначе на некоторых мега-формах можно сильные тормоза получить.

Добавлено через 4 часа 59 минут
Всё странно.

Обработчик события элемента DOM-дерева.
Начинает выполнение в потоке N (не суть конкретная цифра).
Выполняет какие-то действия.

Затем у него случается первый 'await' [1].
Код обработчика почему-то приостанавливается.
После чего всё в том же потоке N приходят подряд - ShouldRender, лог из начала шаблона, лог из конца шаблона (согласно документации да, он должен тут выполнить рендеринг).

Потом снова в потоке N отрабатывает тот код, что в первом 'await' [1].
После чего начинает выполняться (в том же потоке N) следующий 'await' (там для теста Task.Delay).
После этого в другом потоке приходит 'OnAfterRenderAsync'.

А все последующие инструкции изначального обработчика, включая await-ы, уже выполняются в каком-то третьем потоке.
В т.ч. StateHasChanged и всегда неизменно следующие за ним ShouldRender, лог из начала шаблона и лог из конца шаблона.

При этом, если в самом шаблоне добавить 'Thread.Sleep', то первый await [1] не выполняется, пока шаблон не будет полностью отрендерен.

Добавлено через 5 минут
Т.е. если событие пришло от самого Blazor, то он как-то гарантирует, что пока идёт редеринг (от ShouldRender до конца обработки шаблона):
1. Не будут сгенерированы никакие другие события.
2. Все асинхронные обработчики будут каким-то бразом заморожены, пока рендеринг не завершится.

Добавлено через 4 минуты
Т.е. при таком сценарии, получается, в принципе не возможен конфлит доступа к ресурсам.

А вот если событие прилетает откуда-то извне, то именно в процессе рендеринга шаблона можно легко получить исключение 'Collection was modified'.
0
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759
29.12.2022, 09:28  [ТС]
В конечном итоге всё свелось к тому же решению, что я когда-то для WPF делал, т.к. проблема ровно та же, что и там (но дополнительно нужно будет как-то реализовать явное управление циклом рендеринга Blazor).

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

Хотя, для простых случаев, где согласованность состояния не критична, подобный подход можно и не использовать.
0
Уважайте чужое время
75 / 23 / 8
Регистрация: 01.02.2013
Сообщений: 191
03.02.2023, 16:05
Цитата Сообщение от kotelok Посмотреть сообщение
Вопрос больше как синхронизировать доступ к ресурсу в рамках процесса рендеринга блазор-шаблона. Прямо в код шаблона блокировки добавлять ?
Вам бы разделить данные и логику, и тогда все проблемы уйдут.

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

Если же коллекция меняется чаще, чем успевает обработать рендеринг, то тут просто ничего не сделать, человеческий глаз воспринимает не так много изменений в секунду, как Вам, возможно, хотелось бы + пинг до сервера + сама обработка (а если бы каждый запрос, которых миллионы, обрабатывался N секунд?)... Либо у Вас проблема в архитектуре (почему эта коллекция так часто меняется? В чём её смысл? Может быть можно сделать несколько коллекций, чтобы уменьшить кол-во запросов), либо придётся экстраполировать и отдавать грязные данные, как выше описал.

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

Блокировать поток, наверное, тоже может быть приемлемо в некоторых кейсах — это зависит от смысла коллекции.

Всё вышенаписанное — имхо.
1
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759
03.02.2023, 17:54  [ТС]
Цитата Сообщение от big1991 Посмотреть сообщение
Вам бы разделить данные и логику, и тогда все проблемы уйдут.
Не. Тут проблема именно на стороне UI-логики.

Реальный пример - классическое десктопное меню с навигацией при помощи клавиатуры и мыши, реализованное при помощи html-разметки в blazor.

Тестовый сценарий:
1. Откройте обычный виндовый Блокнот (VS не подойдёт, у неё меню модифицированное и ведёт себя немного иначе).
2. Активируйте главное меню (так, чтобы отобразилась выпадающая часть).
3. И далее одновременно:
3.1. Зажмите и удерживайте на клавиатуре кнопку "стрелка влево".
3.2. Мышкой водите по элементам главного меню.

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

И вот тут и проявляются все эти проблемы одновременной модификации коллекций, т.к.:
1. Одновременно от разных div-ов прилетает два события - "onkeydown" и "onpointerenter".
2. Одновременно начинают отрабатывать их обработчики (асинхронные, разумеется), которые в итоге начинают конкурировать за модификацию состояния меню (перевода его из одного состояния в другое).
3. При этом так же может прилететь какое-то внешнее событие, т.е. вне лайфцикла компонента блазора, согласно которому надо, например, скрыть выпадающую часть меню и снять фокус с меню. И это событие ещё и с процессом рендеринга может законфликтовать (а не только с двумя обозначенными выше событиями).
0
Уважайте чужое время
75 / 23 / 8
Регистрация: 01.02.2013
Сообщений: 191
03.02.2023, 18:00
Цитата Сообщение от sau Посмотреть сообщение
задача то типовая и к блазору особо отношения не имеющая
kotelok, т.е. Вы хотите, чтобы сайт (а любое веб-приложение — в фундаментальном смысле такой же сайт, использующий html + css + JS с обвязками) работал со скоростью desktop'а?

Добавлено через 1 минуту
Цитата Сообщение от kotelok Посмотреть сообщение
начинают конкурировать
Ну и разместите выполнение их обработчиков в конкурентную очередь или работайте с блокировкой. Это не проблема, как мне кажется.
1
1338 / 918 / 264
Регистрация: 08.08.2014
Сообщений: 2,759
03.02.2023, 18:14  [ТС]
Цитата Сообщение от big1991 Посмотреть сообщение
Ну и разместите выполнение их обработчиков в конкурентную очередь
Да, в итоге именно такое решение и получилось. Ровно такое же, по сути, как было для WPF-проекта, но дополнительно с полным контролем за циклом перерисовки компонента. Это в итоге сняло необходимость повсюду на блокировки заморачиваться.

Добавлено через 11 минут
Цитата Сообщение от big1991 Посмотреть сообщение
т.е. Вы хотите, чтобы сайт (а любое веб-приложение — в фундаментальном смысле такой же сайт, использующий html + css + JS с обвязками) работал со скоростью desktop'а?
Да, мне нужно чтобы приложение реагировало на клавиатуру/мышь так же быстро, как и аналогичное десктопное решение. Речь именно про отрисовку UI и его отклик на действия пользователя.
0
Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
inter-admin
Эксперт
29715 / 6470 / 2152
Регистрация: 06.03.2009
Сообщений: 28,500
Блог
03.02.2023, 18:14
Помогаю со студенческими работами здесь

Обработчики событий в JS
Добрый вечер. Появился вопрос. Есть такой HTML код. <form action="#" method="post" class="search"> <input...

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

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

Обработчики событий
<button class="btn-1">Число 1</button> <button class="btn-2">Число 2</button> <button class="btn-3">Число 3</button> Добавьте...

Обработчики событий
Предположим имеется форма с 2мя компонентами: button1 и PictureBox1. Для них определены обработчики для событий button1.click и...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
9
Ответ Создать тему
Новые блоги и статьи
PhpStorm 2025.3: WSL Terminal всегда стартует в ~
and_y87 14.12.2025
PhpStorm 2025. 3: WSL Terminal всегда стартует в ~ (home), игнорируя директорию проекта Симптом: После обновления до PhpStorm 2025. 3 встроенный терминал WSL открывается в домашней директории. . .
Access
VikBal 11.12.2025
Помогите пожалуйста !! Как объединить 2 одинаковые БД Access с разными данными.
Новый ноутбук
volvo 07.12.2025
Всем привет. По скидке в "черную пятницу" взял себе новый ноутбук Lenovo ThinkBook 16 G7 на Амазоне: Ryzen 5 7533HS 64 Gb DDR5 1Tb NVMe 16" Full HD Display Win11 Pro
Музыка, написанная Искусственным Интеллектом
volvo 04.12.2025
Всем привет. Некоторое время назад меня заинтересовало, что уже умеет ИИ в плане написания музыки для песен, и, собственно, исполнения этих самых песен. Стихов у нас много, уже вышли 4 книги, еще 3. . .
От async/await к виртуальным потокам в Python
IndentationError 23.11.2025
Армин Ронахер поставил под сомнение async/ await. Создатель Flask заявляет: цветные функции - провал, виртуальные потоки - решение. Не threading-динозавры, а новое поколение лёгких потоков. Откат?. . .
Поиск "дружественных имён" СОМ портов
Argus19 22.11.2025
Поиск "дружественных имён" СОМ портов На странице: https:/ / norseev. ru/ 2018/ 01/ 04/ comportlist_windows/ нашёл схожую тему. Там приведён код на С++, который показывает только имена СОМ портов, типа,. . .
Сколько Государство потратило денег на меня, обеспечивая инсулином.
Programma_Boinc 20.11.2025
Сколько Государство потратило денег на меня, обеспечивая инсулином. Вот решила сделать интересный приблизительный подсчет, сколько государство потратило на меня денег на покупку инсулинов. . . .
Ломающие изменения в C#.NStar Alpha
Etyuhibosecyu 20.11.2025
Уже можно не только тестировать, но и пользоваться C#. NStar - писать оконные приложения, содержащие надписи, кнопки, текстовые поля и даже изображения, например, моя игра "Три в ряд" написана на этом. . .
Мысли в слух
kumehtar 18.11.2025
Кстати, совсем недавно имел разговор на тему медитаций с людьми. И обнаружил, что они вообще не понимают что такое медитация и зачем она нужна. Самые базовые вещи. Для них это - когда просто люди. . .
Создание Single Page Application на фреймах
krapotkin 16.11.2025
Статья исключительно для начинающих. Подходы оригинальностью не блещут. В век Веб все очень привыкли к дизайну Single-Page-Application . Быстренько разберем подход "на фреймах". Мы делаем одну. . .
КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2025, CyberForum.ru