Как стать автором
Обновить

Событийный диспетчер отложенных задач на C#: консолидация и дедупликация данных в текущей инстанции

Уровень сложностиСредний
Время на прочтение4 мин
Количество просмотров2.7K
Всего голосов 5: ↑1 и ↓4-1
Комментарии13

Комментарии 13

Сильно не разбирался, но, нюхом чую какой-то велосипед. :)

Вместо:

Func<List<object>, CancellationToken, Task> taskDelegate = ....

в наше время лучше уже писать:

static Task taskDelegate(List<object> events, CancellationToken cancellationToken) 
{
    ....
}

Со вторым делегатом точно так же. Это называется "local functions".

А вот это:

catch (Exception ex)
{
    ....
    throw new Exception("...");
}

так вообще только джуну простительно - innerException передавать-таки надо.

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

Да и вообще базовый эксепшен выкидывать это моветон. О чем там даже варнинг должен быть от рослина. Тут прям академический пример как не надо делать: максимально безликий эксепшен и похереный стек трейс) Или в иннер завернуть, или голый throw; но не так.

Но тут явно проигнорированы все усилия команды майкрософта) Проверка аргумента в конструкторе на налл - для чего? DI контейнер все равно раньше эксепшен выбросит если сервис в контейнере не найдет. Это мертвый код. К дизайну тоже есть претензии, а List<object> вызывает просто скрежет зубовный, ну не фреймворк же 2.0 на дворе. А если б был, ну хоть бы ArrayList тогда. Ну да ладно, все равно велосипед.

Спасибо за интерес к моей публикации! Давайте разберемся подробнее:

1) Выброс исключения в приведенном примере имеет исключительно поведенческое назначение и осуществляется в "пользовательском коде". Он может быть описан на усмотрение разработчика как угодно, более того, его содержимое в данном случае абсолютно никак не влияет на дальнейшее поведение. У меня VS 2022 PRO v. 17.13.4 с конфигурацией Roslyn по умолчанию и подсказок по этому поводу нет / странно, правда?

2) Спасибо за замечание по поводу выброса исключения при внедрении DI. На самом деле, это базовая защита от ошибок со стороны разработчиков:

- Явное указание на зависимость: Проверка на null явно показывает, что внедряемая зависимость является обязательной для работы класса. Это улучшает читаемость кода и помогает другим разработчикам понять, какие зависимости необходимы для правильной работы класса.
- Предотвращение скрытых ошибок конфигурации DI-контейнера: DI-контейнеры обычно настроены таким образом, чтобы выбрасывать исключение, если не удается разрешить зависимость. Но в некоторых случаях (например, при неправильной конфигурации или при использовании fallback-механизмов) контейнер может внедрить null вместо ожидаемой зависимости. Проверка на null в конструкторе служит дополнительной защитой от таких ситуаций.
- Разработчик может передать null по ошибке, об этом можно прочитать здесь stackoverflow.com/a/72727192/11716490

3) В решении используется List<T>, что обеспечивает безопасность типов, то есть Вы случайно не добавите string к int, object лишь обобщённый пример, лучше было бы указать T?

Разумеется дженерик лучше. По целой куче причин, от контроля типов до производительности.

Вы приводите пример в котором явным образом выброс исключения нарушает все возможные бест практисез по выбросу исключений. Я не могу сказать про конкретные версии студии, и дефолтные настройки, но последние анализаторы рослина ругаются на выброс базового эксепшена точно, а любой дополнительный тул в виде решарпера, или сонаркуба, дополнит про потерянный стек трейс. Не давайте плохих примеров - это мой поинт. Если эксепшен не важен - не бросайте. Если это плейсхолдер - напишите это через коммент // Here is the placeholder for exception handling. И тд. Не давайте плохой код в качестве примера. Разработчик не может по ошибке передать налл в объект, который он не создает, поэтому этот код - мертвый в релизе. Не пишите мертвый для релиза код в проектах, и тем более в примерах, где этот код не важен. Не говоря о том, что ребята из МС озаботились созданием хэлперов и аттрибутов для описания и валидации параметров, и ваш код выглядит по-школьному даже если предположить что эта проверка там нужна. Тогда параметр надо объявить как наллабл, пометить аттрибутом для анализаторов и было бы хорошо воспользоваться хэлпером ArgumentNullException.ThrowIfNull(...); чтобы было по-взрослому.

Не нужно для примера, не важно в контексте - не пишите! Это рекомендация.

Вы заметили много проблем в коде автора, но сейчас вы сами приводите вредный совет. Если параметр объявлен как nullable то как раз таки метод должен мочь с этим null корректно работать. И кидать ArgumentNullException в этом случае не надо. Наоборот, в случае not nullable параметра туда все равно могут передать null, и проверка на null нужна. То что это DI-конструктор еще не означает что его никто не сможет вызвать без DI.

Огромное спасибо за интерес к моей публикации! Давайте разберемся подробнее:

1) Статические функции (методы) в C# не могут содержать ссылки на объекты экземпляра (non-static). Это фундаментальное ограничение, зачем Вы предлагаете осознанно вводить его? Мне такой вариант не подходит.

2) Спасибо за замечание по поводу innerException. Выброс исключения в приведенном примере имеет исключительно поведенческое назначение и осуществляется в "пользовательском коде". Он может быть описан на усмотрение разработчика как угодно, более того, его содержимое в данном случае абсолютно никак не влияет на дальнейшее поведение. По поводу выброса с включением innerException в самом решении, у меня VS 2022 PRO v. 17.13.4 с конфигурацией Roslyn по умолчанию, подсказок по этому поводу нет / странно, правда?

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

Здравствуйте, спасибо за Ваши вопросы по моей публикации!

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


Kafka я рассматривал в качестве решения, но мне хотелось снизить до минимума нагрузку на самописный брокер, поэтому дедупликация в том числе на стороне клиента, ведь масштабирование WebSocket'ов имеет некоторые общеизвестные ограничения https://learn.microsoft.com/en-us/aspnet/core/signalr/scale?view=aspnetcore-8.0.

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

Вообще ничего не понял. Какая основная операция? Что это такое вообще?

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

Стилистика ужасная, взять те же определения Func<List<object>, CancellationToken, Task> в коде методов - кошмар. За собачки в именах переменных тоже плюс не поставлю, но это ладно.

Как вообще из статьи понять, что дедупликация на стороне КЛИЕНТА? Здесь он не упоминается от слова совсем, ну и кода клиента, конечно же, нет.

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

Каких сценариев? Здесь что-то малопонятное (опять) написано в разделе "Альтернативные варианты", но и все. вся статья - жуткая каша.

На закуску: вы тут пишете про какой-то делегат TaskFactory, который содержит кастомную логику. Кастомную логику для чего? Тут он какие-то задачи создает, но в примере он создает задачи-пустышки, ни с чем не связанные. Что они вообще должны делать и на основе чего (что на входе)? Если есть кастомная логика, то должна быть и дефолтная логика. Если она есть, то где она здесь? Зачем для делегата использовать название, которое подходит классу, кстати, во фреймворке есть класс с таким названием, и это один из базовых классов TPL, я бы не стал его название для чего-то другого использовать.

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

Мне упорно кажется что в комментах отвечает нейросеть

Мне кажется статью тоже, человек так коряво не сможет написать. В таком небольшом фрагменте кода столько ляпов.

Было больно читать как текст так и код. В дотнете уже есть готовый механизм на ченелах и куча других очередей.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации