c# - understanding - Contexto de datos EF-Async/Await y Multithreading
run method async c# (2)
Aquí tenemos una situación estancada. AspNetSynchronizationContext
, que es responsable del modelo de subprocesamiento de un entorno de ejecución de API Web ASP.NET, no garantiza que la continuación asincrónica después de la await
se realice en el mismo subproceso. La idea general de esto es hacer que las aplicaciones ASP.NET sean más escalables, por ThreadPool
se bloquean menos hilos de ThreadPool
con operaciones sincrónicas pendientes.
Sin embargo, DataContext
no es seguro para subprocesos, por lo que no se debe usar donde potencialmente se pueda producir un cambio de subprocesos a través de las llamadas a la API de DataContext
. Una construcción de using
separada por llamada asincrónica no ayudará, tampoco:
var something;
using (var dataContext = new DataContext())
{
something = await dataContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
}
Esto se debe a que DataContext.Dispose
podría ejecutarse en un hilo diferente del que el objeto fue originalmente creado, y esto no es algo que DataContext
esperaría.
Si te gusta seguir con la API de DataContext
, llamarla de forma síncrona parece ser la única opción viable. No estoy seguro de si esa declaración debería extenderse a toda la API de EF, pero supongo que cualquier objeto hijo creado con DataContext
API probablemente tampoco sea seguro para subprocesos. Por lo tanto, en ASP.NET, su ámbito de aplicación debería limitarse a la de entre dos llamadas en await
adyacentes.
Podría ser tentador descargar un montón de llamadas DataContext
sincrónicas a un hilo separado con await Task.Run(() => { /* do DataContext stuff here */ })
. Sin embargo, sería un antipatrón , especialmente en el contexto de ASP.NET, donde podría perjudicar el rendimiento y la escalabilidad, ya que no reduciría el número de subprocesos necesarios para cumplir con la solicitud.
Desafortunadamente, si bien la arquitectura asincrónica de ASP.NET es excelente, sigue siendo incompatible con algunas API y patrones establecidos (por ejemplo, aquí hay un caso similar ). Eso es especialmente triste, porque aquí no nos ocupamos del acceso concurrente a la API, es decir, no más de un hilo intenta acceder a un objeto DataContext
al mismo tiempo.
Con suerte, Microsoft abordará eso en las futuras versiones del Framework.
[ACTUALIZACIÓN] A gran escala, sin embargo, podría ser posible descargar la lógica EF a un proceso separado (ejecutado como un servicio WCF) que proporcionaría una API asíncrona segura para subprocesos a la lógica del cliente ASP.NET. Tal proceso puede ser orquestado con un contexto de sincronización personalizado como una máquina de eventos, similar a Node.js. Incluso puede ejecutar un conjunto de apartamentos tipo Node.js, cada apartamento mantiene la afinidad de los objetos EF. Eso permitiría aún beneficiarse de la async EF API.
[ACTUALIZAR] Aquí hay algún intento de encontrar una solución a este problema.
Frecuentemente utilizo async / await para asegurarme de que los hilos de la API web ASP.NET MVC no estén bloqueados por las operaciones de E / S y de red de más larga duración, específicamente las llamadas a la base de datos.
El espacio de nombres System.Data.Entity proporciona una variedad de extensiones de ayuda aquí, como FirstOrDefaultAsync , ContainsAsync , CountAsync , etc.
Sin embargo, dado que los contextos de datos no son seguros para subprocesos, esto significa que el siguiente código es problemático:
var dbContext = new DbContext();
var something = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 1);
var morething = await dbContext.someEntities.FirstOrDefaultAsync(e => e.Id == 2);
De hecho, a veces veo excepciones tales como:
System.InvalidOperationException: la conexión no se cerró. El estado actual de la conexión está abierto.
¿Es el patrón correcto para usar un bloque de uso separado using(new DbContext...)
para cada llamada asincrónica a la base de datos? ¿Es potencialmente más beneficioso simplemente ejecutar síncrono entonces?
La clase DataContext
es parte de LINQ to SQL. No comprende async
/ await
AFAIK, y no debe utilizarse con los métodos de extensión async
Entity Framework.
La clase DbContext
funcionará bien con async
siempre que use EF6 o superior; sin embargo, solo puede tener una operación (sincronización o DbContext
) por instancia de DbContext
ejecute a la vez. Si su código está realmente usando DbContext
, entonces examine la pila de llamadas de su excepción y verifique si hay algún uso simultáneo (por ejemplo, Task.WhenAll
).
Si está seguro de que todo el acceso es secuencial, publique una reproducción mínima o infórmelo como un error en Microsoft Connect.