Какова наилучшая практика в EF Core для использования параллельных асинхронных вызовов с введенным DbContext?
у меня есть API .NET Core 1.1 с EF Core 1.1 и использование Microsoft vanilla setup использования инъекции зависимостей для предоставления DbContext моим службам. (Ссылка: https://docs.microsoft.com/en-us/aspnet/core/data/ef-mvc/intro#register-the-context-with-dependency-injection)
теперь я рассматриваю распараллеливание базы данных как оптимизацию с использованием WhenAll
так вместо:
var result1 = await _dbContext.TableModel1.FirstOrDefaultAsync(x => x.SomeId == AnId);
var result2 = await _dbContext.TableModel2.FirstOrDefaultAsync(x => x.SomeOtherProp == AProp);
I использовать:
var repositoryTask1 = _dbContext.TableModel1.FirstOrDefaultAsync(x => x.SomeId == AnId);
var repositoryTask2 = _dbContext.TableModel2.FirstOrDefaultAsync(x => x.SomeOtherProp == AProp);
(var result1, var result2) = await (repositoryTask1, repositoryTask2 ).WhenAll();
Это все хорошо и хорошо, пока я не использую ту же стратегию вне этих классов доступа к репозиторию БД и не вызову эти же методы с WhenAll в моем контроллере через несколько служб:
var serviceTask1 = _service1.GetSomethingsFromDb(Id);
var serviceTask2 = _service2.GetSomeMoreThingsFromDb(Id);
(var dataForController1, var dataForController2) = await (serviceTask1, serviceTask2).WhenAll();
теперь, когда я вызываю это с моего контроллера, случайно я получу ошибки параллелизма, такие как:
2 ответов
дошло до того, что единственным способом ответить на дебаты было сделать тест производительности/нагрузки, чтобы получить сопоставимые, эмпирические, статистические данные, чтобы я мог решить это раз и навсегда.
вот что я проверил:
Cloud Load test с VSTS @ 200 users max в течение 4 минут на стандартном веб-приложении Azure.
тест #1: 1 вызов API с инъекцией зависимостей DbContext и async/await для каждой службы.
Тест #2: 1 вызов API с новым созданием DbContext в каждом вызове метода службы и использованием параллельного выполнения потока с WhenAll.
вывод:
для тех, кто сомневается в результатах, я провел эти тесты несколько раз с различными пользовательскими нагрузками, и средние значения были в основном одинаковыми каждый раз.
увеличения представления с параллелью обработка, на мой взгляд, незначительна, и это не оправдывает необходимость отказа от инъекции зависимостей, которая создала бы накладные расходы на разработку/обслуживание долга, потенциал для ошибок, если обрабатываются неправильно, и отход от официальных рекомендаций Microsoft.
еще одна вещь, чтобы отметить: как вы можете видеть, на самом деле было несколько неудачных запросов со стратегией WhenAll, даже при обеспечении нового контекста создается каждый раз. Я не уверен, что причина это, но я бы предпочел не 500 ошибок над увеличением производительности 10ms.
используя какие-либо context.XyzAsync()
метод полезен только если вы либо await
вызываемый метод или возвращает управление вызывающему потоку это не context
в область.
A DbContext
экземпляр не является потокобезопасным: вы никогда не должны использовать его в параллельных потоках. Это означает, что точно, никогда не используйте его в нескольких потоках, даже если они не работают параллельно. Не пытайтесь обойти это.
если почему вы хотите работать параллельные операции с базой данных (и думаю, вы можете избежать тупиков, конфликтов параллелизма и т. д.), убедитесь, что каждый из них имеет свои DbContext
экземпляра. Однако обратите внимание, что распараллеливание в основном полезно для процессов, связанных с ЦП, а не для процессов, связанных с IO, таких как взаимодействие с базой данных. Возможно, вы можете извлечь выгоду из параллельной независимой читать операции, но я бы, конечно, никогда не выполняются параллельно написать процессы. Помимо тупиков и т. д. это также делает его гораздо сложнее выполнить все операции в одной транзакции.
In ASP.Net core вы обычно используете шаблон context-per-request (ServiceLifetime.Scoped
см. здесь), но даже это не может помешать вам перенести контекст на несколько потоков. В конце концов, только программист может предотвратить это.
если вы беспокоитесь о производительности затрат на создание новых контекстов все время: не будьте. Создание контекста является облегченной операцией, поскольку модель (модель хранилища, концептуальная модель + сопоставления между ними) создается один раз, а затем сохраняется в домене приложения. Кроме того, новый контекст не создает физического соединения с базой данных. Все ASP.Net операции с базой данных выполняются через пул соединений, который управляет пулом физических соединений.
если все это означает, что вам нужно перенастроить DI, чтобы согласовать с лучшими практиками, так тому и быть. Если ваша текущая настройка передает контексты нескольким потокам, произошел плохой проектное решение в прошлом. Сопротивляйтесь искушению отложить неизбежный рефакторинг обходными путями. Единственный обходной путь-распараллелить ваш код, поэтому в конце концов он может быть даже медленнее, чем если вы перепроектируете свой DI и код придерживаться контекста в потоке.