Упрощаем работу с коллекциями в C# с помощью Collection Expressions
В C# 12 появились Collection Expressions — выражения коллекций, которые позволяют создавать коллекции с помощью простого и лаконичного синтаксиса
Вместо длинной записи можно использовать квадратные скобки, делая код чище и понятнее.
👉 @KodBlog
В C# 12 появились Collection Expressions — выражения коллекций, которые позволяют создавать коллекции с помощью простого и лаконичного синтаксиса
[].Вместо длинной записи можно использовать квадратные скобки, делая код чище и понятнее.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5👍3👎1👏1🍾1
7 ошибок с async/await, которые допускают даже Senior .NET-разработчики
После нескольких лет работы с production-приложениями на .NET становится понятно: написать асинхронный код несложно. Гораздо сложнее сделать так, чтобы он правильно работал под высокой нагрузкой.
Вот 7 ошибок, которых стоит избегать.
1. Блокировка асинхронного кода через
Как правильно: использовать
2. Использование
Как правильно: возвращать
3. Забывать
Как правильно: использовать
4. Выполнять независимые операции последовательно. Представим, что нужно сделать два независимых API-запроса. При последовательном выполнении второй начнётся только после завершения первого.
Как правильно: запускать обе операции и ожидать их завершения вместе с помощью
5. Игнорировать
Как правильно: принимать
6. Считать, что async означает параллельное выполнение.
Как правильно: использовать
7. Использовать
Как правильно: ограничивать количество одновременно выполняемых операций с помощью
Главное: async/await — это не просто способ повысить производительность. Это инструмент управления ресурсами, параллелизмом, отменой операций и обработкой ошибок.
Метод может быть асинхронным, но при этом замедлять приложение, снижать его надёжность и усложнять отладку.
👉 @KodBlog
После нескольких лет работы с production-приложениями на .NET становится понятно: написать асинхронный код несложно. Гораздо сложнее сделать так, чтобы он правильно работал под высокой нагрузкой.
Вот 7 ошибок, которых стоит избегать.
1. Блокировка асинхронного кода через
.Result или .Wait(). Вызовы .Result и .Wait() блокируют текущий поток в ожидании завершения асинхронной операции. В ASP.NET Core это может привести к исчерпанию потоков Thread Pool (Thread Pool Starvation) и снижению пропускной способности приложения.Как правильно: использовать
await на протяжении всей цепочки вызовов.2. Использование
async void вместо async Task. Метод с async void нельзя ожидать через await, а его исключения нельзя перехватить вызывающим кодом с помощью стандартного механизма обработки исключений на основе Task.Как правильно: возвращать
Task или Task<T>. async void стоит использовать только для обработчиков событий, которым необходим такой формат.3. Забывать
await при вызове асинхронного метода. Если вызвать асинхронный метод без await и не отслеживать возвращённый Task, можно потерять исключения или получить операцию, которая продолжит выполняться за пределами ожидаемого жизненного цикла.Как правильно: использовать
await или явно управлять жизненным циклом задачи.4. Выполнять независимые операции последовательно. Представим, что нужно сделать два независимых API-запроса. При последовательном выполнении второй начнётся только после завершения первого.
Как правильно: запускать обе операции и ожидать их завершения вместе с помощью
Task.WhenAll. Это подходит, если операции действительно независимы и безопасны для параллельного выполнения.5. Игнорировать
CancellationToken. Если не передавать сигнал отмены дальше по цепочке вызовов, ресурсоёмкие запросы к базе данных или HTTP-запросы могут продолжать выполняться даже после отключения клиента.Как правильно: принимать
CancellationToken и передавать его через всю цепочку асинхронных вызовов.6. Считать, что async означает параллельное выполнение.
async/await автоматически не создаёт новые потоки и не ускоряет вычислительно сложные операции. Асинхронность в первую очередь позволяет не блокировать поток во время ожидания.Как правильно: использовать
async для операций ввода-вывода (I/O-bound). Task.Run может подойти для некоторых CPU-bound задач, но его применение в обработчиках серверных запросов требует осторожности.7. Использовать
Task.WhenAll без ограничения параллелизма. Одновременный запуск тысяч запросов к базе данных или HTTP-запросов может исчерпать пулы соединений, перегрузить внешние сервисы и увеличить задержки.Как правильно: ограничивать количество одновременно выполняемых операций с помощью
SemaphoreSlim, Parallel.ForEachAsync или других механизмов управления конкурентностью.Главное: async/await — это не просто способ повысить производительность. Это инструмент управления ресурсами, параллелизмом, отменой операций и обработкой ошибок.
Метод может быть асинхронным, но при этом замедлять приложение, снижать его надёжность и усложнять отладку.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤8😐4🍾1
BenchmarkDotNet в .NET Single-File приложениях
Бенчмарки — отличный сценарий использования одnofайловых приложений (Single-File Apps) в .NET.
В примере показано, как использовать BenchmarkDotNet в таком приложении. Самые важные настройки находятся в начале кода, а также стоит обратить внимание на атрибут
Автор делится примером реализации и готов отправить исходный код тем, кто заинтересуется им в комментариях.
👉 @KodBlog
Бенчмарки — отличный сценарий использования одnofайловых приложений (Single-File Apps) в .NET.
В примере показано, как использовать BenchmarkDotNet в таком приложении. Самые важные настройки находятся в начале кода, а также стоит обратить внимание на атрибут
[InProcess].Автор делится примером реализации и готов отправить исходный код тем, кто заинтересуется им в комментариях.
Please open Telegram to view this post
VIEW IN TELEGRAM
🍾4
CQRS без Event Sourcing: используйте сильные стороны EF Core и Dapper
Чтобы получить преимущества CQRS, не обязательно внедрять полноценный Event Sourcing.
Достаточно разделить модели записи и чтения:
• EF Core — для бизнес-правил, доменных агрегатов и операций записи.
• Dapper — для быстрых SQL-запросов и чтения данных.
Такой подход позволяет сохранить бизнес-логику в одном месте, а запросы к данным сделать проще и производительнее.
Прагматично, чисто и без лишней сложности.
👉 @KodBlog
Чтобы получить преимущества CQRS, не обязательно внедрять полноценный Event Sourcing.
Достаточно разделить модели записи и чтения:
• EF Core — для бизнес-правил, доменных агрегатов и операций записи.
• Dapper — для быстрых SQL-запросов и чтения данных.
Такой подход позволяет сохранить бизнес-логику в одном месте, а запросы к данным сделать проще и производительнее.
Прагматично, чисто и без лишней сложности.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍5🍾2
Message Brokers гарантируют At-Least-Once, а не Exactly-Once
Что произойдёт, если ваш consumer обработает одно и то же событие дважды? Сможет ли система корректно справиться с повторной доставкой?
Брокеры сообщений часто используют семантику доставки At-Least-Once — событие будет доставлено как минимум один раз, но возможны дубликаты.
Поэтому обработчики событий должны быть идемпотентными (Idempotent): повторная обработка одного и того же события не должна приводить к повторным побочным эффектам.
Разберём, как проектировать идемпотентные обработчики событий и защищать систему от дублирующей обработки.
👉 @KodBlog
Что произойдёт, если ваш consumer обработает одно и то же событие дважды? Сможет ли система корректно справиться с повторной доставкой?
Брокеры сообщений часто используют семантику доставки At-Least-Once — событие будет доставлено как минимум один раз, но возможны дубликаты.
Поэтому обработчики событий должны быть идемпотентными (Idempotent): повторная обработка одного и того же события не должна приводить к повторным побочным эффектам.
Разберём, как проектировать идемпотентные обработчики событий и защищать систему от дублирующей обработки.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤2🍾1
Зачем обрабатывать элементы по одному, если процессор может обрабатывать сразу 8?
В C# есть инструменты для использования SIMD (Single Instruction, Multiple Data) — технологии, которая позволяет выполнять одну операцию одновременно над несколькими элементами данных.
Для этого можно использовать Vector<T> и Hardware Intrinsics.
Что они дают?
Vector<T> — позволяет обрабатывать несколько значений одного типа за одну операцию.
Hardware Intrinsics — дают более низкоуровневый доступ к SIMD-инструкциям процессора, например AVX2 и AVX-512.
SIMD — ускоряет вычисления над массивами чисел, обработку изображений, математические операции и другие задачи с однотипными данными.
И всё это доступно прямо в C# — без необходимости переходить на C++.
👉 @KodBlog
В C# есть инструменты для использования SIMD (Single Instruction, Multiple Data) — технологии, которая позволяет выполнять одну операцию одновременно над несколькими элементами данных.
Для этого можно использовать Vector<T> и Hardware Intrinsics.
Что они дают?
Vector<T> — позволяет обрабатывать несколько значений одного типа за одну операцию.
Hardware Intrinsics — дают более низкоуровневый доступ к SIMD-инструкциям процессора, например AVX2 и AVX-512.
SIMD — ускоряет вычисления над массивами чисел, обработку изображений, математические операции и другие задачи с однотипными данными.
И всё это доступно прямо в C# — без необходимости переходить на C++.
Please open Telegram to view this post
VIEW IN TELEGRAM
🤔5👍2🔥2🍾1
Учитывают ли ваши C#-структуры кэш-линии процессора?
Неправильное расположение полей структуры приводит к неэффективному использованию памяти и дополнительным затратам на доступ к кэш-линии процессора.
Используйте
👉 @KodBlog
Неправильное расположение полей структуры приводит к неэффективному использованию памяти и дополнительным затратам на доступ к кэш-линии процессора.
Используйте
[StructLayout(LayoutKind.Sequential, Pack = 1)], чтобы контролировать размещение полей в памяти.Please open Telegram to view this post
VIEW IN TELEGRAM
❤2🤔2👏1🍾1
Каждый раз, когда асинхронный метод возвращает
Если ваш метод часто завершается синхронно (например, при попадании в кеш), используйте
Это поможет избежать миллионов лишних выделений памяти (heap allocations).
👉 @KodBlog
Task<T>, при завершении операции выделяется память в куче.Если ваш метод часто завершается синхронно (например, при попадании в кеш), используйте
ValueTask<T>.Это поможет избежать миллионов лишних выделений памяти (heap allocations).
Please open Telegram to view this post
VIEW IN TELEGRAM
🍾4
Запускаете несколько реплик фонового воркера в Kubernetes?
Локальные блокировки (
Используйте распределённые блокировки Redis (Redis Distributed Locks) с fencing-токенами, чтобы предотвратить состояния гонки (race conditions) и избежать одновременного выполнения одной задачи несколькими воркерами.
👉 @KodBlog
Локальные блокировки (
lock / Monitor) не работают между разными контейнерами.Используйте распределённые блокировки Redis (Redis Distributed Locks) с fencing-токенами, чтобы предотвратить состояния гонки (race conditions) и избежать одновременного выполнения одной задачи несколькими воркерами.
Please open Telegram to view this post
VIEW IN TELEGRAM
❤3❤🔥2🍾2
Task.WhenAll vs Parallel.ForEachAsync в .NET: что выбрать?
Оба инструмента позволяют выполнять независимые операции конкурентно, но отличаются способом управления параллельностью и ресурсами.
Правильный выбор зависит от типа нагрузки.
1. Task.WhenAll. Лучше всего подходит для известного набора независимых асинхронных операций, особенно связанных с вводом-выводом (I/O-bound).
• Идеален для нескольких независимых запросов к API или базе данных.
• Не имеет встроенного ограничения количества одновременно выполняемых операций.
• Не создаёт потоки автоматически.
2. Parallel.ForEachAsync. Лучше всего подходит для обработки коллекций с контролируемым уровнем параллельности.
• Поддерживает I/O-bound и CPU-bound нагрузки.
• Имеет встроенный механизм ограничения параллельности.
• Полезен при обработке больших коллекций и выполнении внешних API-запросов.
3. Что выбрать?
• CPU-bound: предпочитайте
• I/O-bound: используйте
Внешние API: подходят оба варианта, но
Важное напоминание об EF Core:
Никогда не выполняйте параллельные запросы через один и тот же экземпляр
Главное:
•
•
И помните: по умолчанию
Какой из этих инструментов вы чаще используете в production-приложениях на .NET?
👉 @KodBlog
Оба инструмента позволяют выполнять независимые операции конкурентно, но отличаются способом управления параллельностью и ресурсами.
Правильный выбор зависит от типа нагрузки.
1. Task.WhenAll. Лучше всего подходит для известного набора независимых асинхронных операций, особенно связанных с вводом-выводом (I/O-bound).
var usersTask = GetUsersAsync();
var ordersTask = GetOrdersAsync();
await Task.WhenAll(usersTask, ordersTask);
• Идеален для нескольких независимых запросов к API или базе данных.
• Не имеет встроенного ограничения количества одновременно выполняемых операций.
• Не создаёт потоки автоматически.
2. Parallel.ForEachAsync. Лучше всего подходит для обработки коллекций с контролируемым уровнем параллельности.
await Parallel.ForEachAsync(
orders,
new ParallelOptions
{
MaxDegreeOfParallelism = 10
},
async (order, ct) =>
await ProcessOrderAsync(order, ct));
• Поддерживает I/O-bound и CPU-bound нагрузки.
• Имеет встроенный механизм ограничения параллельности.
• Полезен при обработке больших коллекций и выполнении внешних API-запросов.
3. Что выбрать?
• CPU-bound: предпочитайте
Parallel.ForEach для синхронных операций с высокой вычислительной нагрузкой.• I/O-bound: используйте
Task.WhenAll для нескольких независимых операций или Parallel.ForEachAsync, если необходимо ограничить параллельность.Внешние API: подходят оба варианта, но
Parallel.ForEachAsync часто удобнее для обработки сотен запросов без чрезмерной нагрузки на внешние сервисы.Важное напоминание об EF Core:
DbContext не является потокобезопасным (thread-safe).Никогда не выполняйте параллельные запросы через один и тот же экземпляр
DbContext. Используйте отдельные экземпляры или выполняйте запросы последовательно.Главное:
•
Task.WhenAll ожидает завершения нескольких задач без встроенного ограничения параллельности.•
Parallel.ForEachAsync обрабатывает коллекции с ограниченным уровнем параллельности.И помните: по умолчанию
Parallel.ForEachAsync допускает количество одновременно выполняемых операций, равное Environment.ProcessorCount. Настраивайте MaxDegreeOfParallelism с учётом особенностей вашей нагрузки.Какой из этих инструментов вы чаще используете в production-приложениях на .NET?
Please open Telegram to view this post
VIEW IN TELEGRAM
❤5🍾1
CQRS ≠ MediatR.
Перестаньте смешивать CQRS и MediatR.
То, что вы используете MediatR, не означает, что у вас реализован CQRS.
CQRS — это про разделение операций чтения и записи, часто с разными моделями, стратегиями хранения и иногда с разными хранилищами данных.
MediatR — это библиотека для диспетчеризации сообщений внутри процесса. Она удобна для организации команд и запросов, но это не фреймворк CQRS.
Можно использовать MediatR в архитектуре, построенной по CQRS.
Но не стоит считать, что одно автоматически означает другое.
Сначала разберитесь в различии, прежде чем внедрять паттерн или инструмент.
👉 @KodBlog
Перестаньте смешивать CQRS и MediatR.
То, что вы используете MediatR, не означает, что у вас реализован CQRS.
CQRS — это про разделение операций чтения и записи, часто с разными моделями, стратегиями хранения и иногда с разными хранилищами данных.
MediatR — это библиотека для диспетчеризации сообщений внутри процесса. Она удобна для организации команд и запросов, но это не фреймворк CQRS.
Можно использовать MediatR в архитектуре, построенной по CQRS.
Но не стоит считать, что одно автоматически означает другое.
Сначала разберитесь в различии, прежде чем внедрять паттерн или инструмент.
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6👏1🍾1