Какова наилучшая практика в 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 для каждой службы.

результаты теста #1:enter image description here

Тест #2: 1 вызов API с новым созданием DbContext в каждом вызове метода службы и использованием параллельного выполнения потока с WhenAll.

результаты теста №2:enter image description here

вывод:

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

увеличения представления с параллелью обработка, на мой взгляд, незначительна, и это не оправдывает необходимость отказа от инъекции зависимостей, которая создала бы накладные расходы на разработку/обслуживание долга, потенциал для ошибок, если обрабатываются неправильно, и отход от официальных рекомендаций 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 и код придерживаться контекста в потоке.