TGViewer
.NET Разработчик .NET Разработчик @netdeveloperdiary · 6.75K subscribers
Post #3011 2.33K
День 2508. #ЗаметкиНаПолях
DbContext не Является Потокобезопасным. Распараллеливаем Запросы EF Core Правильно. Начало
Все мы создавали конечные точки вроде «Панель управления для мониторинга» или «Сводка по пользователю». Это конечная точка, которой необходимо получить несколько совершенно не связанных между собой наборов данных, чтобы составить полную картину для пользователя. Например, последние 50 заказов, текущие логи состояния системы, настройки профиля пользователя и, возможно, количество уведомлений.

Итак, вы пишете код стандартным способом:
var orders = await GetRecentOrdersAsync(id);
var logs = await GetSystemLogsAsync();
var stats = await GetUserStatsAsync(id);

return new DashboardDto(orders, logs, stats);

Это чисто, читаемо, работает. Но есть проблема. Если GetRecentOrdersAsync занимает 300мс, GetSystemLogsAsync — 400мс, а GetUserStatsAsync — 300мс, данные будут загружаться целую секунду (300+400+300).

В распределённой системе задержка ухудшает пользовательский опыт. Поскольку эти наборы данных не связаны между собой, мы должны иметь возможность выполнять их параллельно. Тогда общее время составило бы только длительность самого медленного запроса (400 мс). Это 60% улучшение производительности просто за счёт изменения способа выполнения кода. Но наивный подход с EF Core не сработает.

Проблема с Task.WhenAll
Самая распространённая ошибка – обернуть эти вызовы репозитория в задачи и ожидать их выполнения одновременно:
// ❌ НЕ ДЕЛАЙТЕ ТАК
public async Task<DashboardData>
GetDashboardData(int id)
{
// Методы используют один _dbContext
var orders = _repo.GetOrdersAsync(id);
var logs = _repo.GetLogsAsync();
var stats = _repo.GetStatsAsync(id);

await Task.WhenAll(orders, logs, stats);

return new DashboardData(
orders.Result,
logs.Result,
stats.Result);
}

Если вы выполните это, вы столкнётесь с таким исключением: «A second operation started on this context before a previous operation completed. This is usually caused by different threads using the same instance of DbContext, however instance members are not guaranteed to be thread safe.» («Вторая операция в этом контексте началась до завершения предыдущей. Обычно это происходит из-за того, что разные потоки используют один и тот же экземпляр DbContext, однако потокобезопасность членов экземпляра не гарантируется.»)

Почему это происходит?
DbContext в EF Core не является потокобезопасным. Это объект с состоянием, предназначенный для управления одной единицей работы. Он поддерживает «отслеживание изменений» для контроля за загруженными сущностями и инкапсулирует единственное базовое соединение с БД.

Протоколы баз данных (например, TCP-поток для PostgreSQL или SQL Server) обычно синхронны на уровне соединения. Вы не можете отправить два разных SQL-запроса по одному и тому же каналу в одну и ту же миллисекунду. При использовании Task.WhenAll несколько потоков пытаются одновременно захватить это единственное соединение, и EF Core вмешивается, чтобы сгенерировать исключение и предотвратить повреждение данных.

Таким образом, у нас возникает дилемма: нам нужна скорость параллелизма, но DbContext вынуждает нас использовать последовательное выполнение.

Окончание следует…

Источник:
https://www.milanjovanovic.tech/blog/dbcontext-is-not-thread-safe-parallelizing-ef-core-queries-the-right-way
  • 👍 31
More from @netdeveloperdiary
  1. Sep 29, 2026Фото 3 (с) Анатолий Кулаков
  2. Sep 29, 2026День 2799. Конференция DotNext 2026. Часть 1 25 и 26 сентября в Москве прошла очередная ко…
  3. Sep 28, 2026День 2798. #Оффтоп Утиная Типизация в C# с Помощью Перехватчиков. Часть 2 Некоторое время…
  4. Sep 27, 2026День 2797. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Окончание Начало Продол…
  5. Sep 26, 2026День 2796. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Продолжение Начало Три…
  6. Sep 25, 2026День 2795. #ЗаметкиНаПолях #AI Рабочий процесс с Copilot для .NET. Начало Проблема с позиц…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →