Контекст данных EF – Async / Await & Multithreading

Я часто использую async / await, чтобы гарантировать, что streamи ASP.NET MVC Web API не блокируются более длительными операциями ввода-вывода и сети, особенно вызовы базы данных.

Пространство имен System.Data.Entity предоставляет множество вспомогательных расширений здесь, таких как FirstOrDefaultAsync , ContainsAsync , CountAsync и т. Д.

Однако, поскольку контексты данных не являются streamобезопасными, это означает, что следующий код является проблематичным:

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...) для каждого асинхронного вызова в базу данных? Возможно ли более выгодно вместо этого выполнять синхронное?

Здесь у нас есть тупиковая ситуация. AspNetSynchronizationContext , который отвечает за модель streamовой обработки среды выполнения ASP.NET Web API, не гарантирует, что асинхронное продолжение после await будет происходить в одном streamе. Вся идея заключается в том, чтобы сделать приложения ASP.NET более масштабируемыми, поэтому меньше streamов ThreadPool блокируются в ожидании синхронных операций.

Однако DataContext не является streamобезопасным, поэтому его не следует использовать, когда streamовый коммутатор может потенциально возникать в вызовах API DataContext . Отдельная using конструкция для асинхронного вызова не поможет:

 var something; using (var dataContext = new DataContext()) { something = await dataContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1); } 

Это потому, что DataContext.Dispose может выполняться в другом streamе от того, из которого был создан объект, и это не то, что ожидал DataContext .

Если вам нравится придерживаться API DataContext , вызов его синхронно представляется единственным возможным вариантом. Я не уверен, что это утверждение должно быть распространено на весь API EF, но я полагаю, что любые дочерние объекты, созданные с помощью DataContext API, также не являются streamобезопасными. Таким образом, в ASP.NET их using scope должно быть ограничено тем, что между двумя соседними await .

Может возникнуть соблазн выгрузить кучу синхронных вызовов DataContext в отдельный stream с await Task.Run(() => { /* do DataContext stuff here */ }) . Тем не менее, это будет известный анти-шаблон , особенно в контексте ASP.NET, где это может только повредить производительность и масштабируемость, поскольку это не уменьшит количество streamов, необходимых для выполнения запроса.

К сожалению, хотя асинхронная архитектура ASP.NET велика, она остается несовместимой с некоторыми установленными API и шаблонами (например, вот аналогичный случай ). Это особенно печально, потому что мы не имеем дело с одновременным доступом к API здесь, то есть не более одного streamа пытается одновременно получить доступ к объекту DataContext .

Надеюсь, Microsoft рассмотрит это в будущих версиях Framework.

[UPDATE] В широком масштабе, однако, может быть возможно разгрузить логику EF отдельному процессу (выполняемому как служба WCF), который обеспечил бы безопасный asynchronous API-интерфейс для клиентской логики ASP.NET. Такой процесс может быть организован с помощью настраиваемого контекста синхронизации как машины событий, аналогичной Node.js. Он может даже запускать пул квартир, похожих на Node.js, причем каждая квартира поддерживает сходство streamов для объектов EF. Это позволит по-прежнему извлекать выгоду из API асинхронного EF.

[UPDATE] Вот несколько попыток найти решение этой проблемы.

Класс DataContext является частью LINQ to SQL. Он не понимает async / await AFAIK и не должен использоваться с методами расширения async Framework Entity Framework.

Класс DbContext будет отлично работать с async до тех пор, пока вы используете EF6 или выше; однако вы можете иметь только одну операцию (синхронизацию или асинхронную синхронизацию) на экземпляр DbContext запущенный одновременно. Если ваш код на самом деле использует DbContext , то изучите стек вызовов вашего исключения и проверьте наличие параллельного использования (например, Task.WhenAll ).

Если вы уверены, что весь доступ последователен, то, пожалуйста, опубликуйте минимальный реестр и / или сообщите об этом как ошибку в Microsoft Connect.

  • Объективный-C-asynchronous веб-запрос с файлами cookie
  • Разница между wait () и sleep ()
  • Неблокирующий pthread_join
  • Проблемы с добавлением JTextArea
  • Как я должен тестировать многопоточный код?
  • Есть ли способ настройки культуры для целого приложения? Все текущие streamи и новые streamи?
  • Как приостановить / скрыть stream или процесс в Android?
  • Сколько памяти занимает мой stream java?
  • В чем разница между «статическим» и «динамическим» расписанием в OpenMP?
  • JavaFX запускает другое приложение
  • Когда вы используете ключевое слово volatile в Java?
  • Давайте будем гением компьютера.