Контекст данных EF - асинхронное / ожидание и многопоточность
Я часто использую async/await, чтобы гарантировать, что потоки веб-API ASP.NET MVC не блокируются более длительными операциями ввода-вывода и сетевыми операциями, в частности вызовами базы данных.
Пространство имен System.Data.Entity предоставляет множество вспомогательных расширений, таких как FirstOrDefaultAsync, ContainsAsync, CountAsync и так далее.
Однако, поскольку контексты данных не являются потокобезопасными, это означает, что следующий код проблематичен:
var dbContext = new DbContext();
var something = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);
На самом деле, я иногда вижу исключения, такие как:
System.InvalidOperationException: соединение не было закрыто. Текущее состояние соединения открыто.
Является ли правильный шаблон затем использовать отдельный using(new DbContext...)
блокировать для каждого асинхронного обращения к базе данных? Потенциально более выгодно просто выполнять синхронно, чем вместо этого?
3 ответа
У нас здесь тупиковая ситуация. AspNetSynchronizationContext
, который отвечает за многопоточную модель среды выполнения ASP.NET Web API, не гарантирует, что асинхронное продолжение после await
будет проходить в той же теме. Идея заключается в том, чтобы сделать приложения ASP.NET более масштабируемыми, чтобы меньше потоков ThreadPool
заблокированы в ожидании синхронных операций.
Тем не менее DataContext
Класс (часть LINQ to SQL) не является потокобезопасным, поэтому его не следует использовать там, где может произойти переключение потока через DataContext
Вызовы API. Отдельный using
Конструкция для асинхронного вызова также не поможет:
var something;
using (var dataContext = new DataContext())
{
something = await dataContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
}
Это потому что DataContext.Dispose
может выполняться в потоке, отличном от того, в котором изначально был создан объект, и это не DataContext
будет ожидать.
Если вам нравится придерживаться DataContext
API, вызывая его синхронно, представляется единственно возможным вариантом. Я не уверен, должно ли это утверждение быть распространено на весь EF API, но я предполагаю, что любые дочерние объекты, созданные с DataContext
API, вероятно, также не является поточно-ориентированным. Таким образом, в ASP.NET их using
область действия должна быть ограничена областью между двумя соседними await
звонки.
Может быть заманчиво разгрузить кучу синхронных DataContext
звонки в отдельную ветку с await Task.Run(() => { /* do DataContext stuff here */ })
, Тем не менее, это было бы известным анти-паттерном, особенно в контексте ASP.NET, где это могло бы только снизить производительность и масштабируемость, поскольку это не уменьшило бы количество потоков, необходимых для выполнения запроса.
К сожалению, хотя асинхронная архитектура ASP.NET великолепна, она по-прежнему несовместима с некоторыми установленными API и шаблонами (например, здесь аналогичный случай). Это особенно грустно, потому что мы не имеем дело с одновременным доступом к API здесь, т.е. не более чем один поток пытается получить доступ к DataContext
объект одновременно.
Надеемся, что Microsoft решит эту проблему в будущих версиях Framework.
[ОБНОВЛЕНИЕ] Тем не менее, в больших масштабах, возможно, удастся разгрузить логику EF в отдельный процесс (запущенный как служба WCF), который предоставит поточно-безопасный асинхронный API-интерфейс для клиентской логики ASP.NET. Такой процесс может быть организован с настраиваемым контекстом синхронизации как машина событий, подобная Node.js. Он может даже управлять пулом Node.js-подобных квартир, каждая из которых поддерживает сродство потоков к объектам EF. Это позволило бы по-прежнему пользоваться асинхронным EF API.
[ОБНОВЛЕНИЕ] Вот некоторая попытка найти решение этой проблемы.
DataContext
Класс является частью LINQ to SQL. Не понимает async
/await
AFAIK, и не должен использоваться с Entity Framework async
методы расширения.
DbContext
класс будет хорошо работать с async
до тех пор, пока вы используете EF6 или выше; тем не менее, вы можете иметь только одну операцию (синхронную или асинхронную) DbContext
экземпляр работает одновременно. Если ваш код на самом деле использует DbContext
, затем проверьте стек вызовов вашего исключения и проверьте наличие одновременного использования (например, Task.WhenAll
).
Если вы уверены, что все права доступа являются последовательными, то, пожалуйста, опубликуйте минимальное повторение и / или сообщите об этом как об ошибке в Microsoft Connect.
Асинхронное программирование - это средство параллельного программирования, в котором единица работы выполняется отдельно от основного потока приложения и уведомляет вызывающий поток о его завершении, сбое или прогрессе. Основными преимуществами, которые можно получить от использования асинхронного программирования, являются повышение производительности приложений и быстроты реагирования.
Entity Framework 6.0 поддерживает асинхронное программирование, это означает, что асинхронно вы можете запрашивать данные и сохранять данные. Используя async/await, вы можете легко написать асинхронное программирование для структуры сущностей.
Пример:
public async Task<Project> GetProjectAsync(string name)
{
DBContext _localdb = new DBContext();
return await _localdb.Projects.FirstOrDefaultAsync(x => x.Name == name);
}
https://rajeevdotnet.blogspot.com/2018/06/entity-framework-asynchronous.html