He estado buscando las diferencias entre los 2 pares anteriores, pero no he encontrado ningún artículo que explique claramente al respecto ni cuándo usar uno u otro.
Entonces, ¿cuál es la diferencia entre SaveChanges()
y SaveChangesAsync()
?
¿Y entre Find()
y FindAsync()
?
En el lado del servidor, cuando usamos Async
métodos, también necesitamos agregar await
. Por lo tanto, no creo que sea asincrónico en el lado del servidor.
¿Solo ayuda a evitar el bloqueo de la interfaz de usuario en el navegador del lado del cliente? ¿O hay pros y contras entre ellos?
c#
entity-framework
async-await
Hien Tran
fuente
fuente
Respuestas:
Cada vez que necesite realizar una acción en un servidor remoto, su programa genera la solicitud, la envía y luego espera una respuesta. Usaré
SaveChanges()
ySaveChangesAsync()
como ejemplo, pero lo mismo se aplica aFind()
yFindAsync()
.Supongamos que tiene una lista
myList
de más de 100 elementos que necesita agregar a su base de datos. Para insertar eso, su función se vería así:using(var context = new MyEDM()) { context.MyTable.AddRange(myList); context.SaveChanges(); }
Primero crea una instancia de
MyEDM
, agrega la listamyList
a la tablaMyTable
, luego llamaSaveChanges()
para persistir los cambios en la base de datos. Funciona como lo desea, los registros se confirman, pero su programa no puede hacer nada más hasta que finalice la confirmación. Esto puede llevar mucho tiempo dependiendo de lo que esté cometiendo. Si está realizando cambios en los registros, la entidad tiene que confirmarlos uno a la vez (¡una vez tuve que guardar 2 minutos para las actualizaciones)!Para resolver este problema, puede hacer una de dos cosas. La primera es que puede iniciar un nuevo hilo para manejar el inserto. Si bien esto liberará el hilo de llamada para continuar ejecutándose, creó un nuevo hilo que simplemente se quedará allí y esperará. No hay necesidad de esa sobrecarga, y esto es lo
async await
que resuelve el patrón.Para operaciones de E / S,
await
se convierte rápidamente en su mejor amigo. Tomando la sección de código de arriba, podemos modificarla para que sea:using(var context = new MyEDM()) { Console.WriteLine("Save Starting"); context.MyTable.AddRange(myList); await context.SaveChangesAsync(); Console.WriteLine("Save Complete"); }
Es un cambio muy pequeño, pero tiene efectos profundos en la eficiencia y el rendimiento de su código. ¿Así que lo que pasa? El comienzo del código es el mismo, crea una instancia de
MyEDM
y agrega sumyList
aMyTable
. ¡Pero cuando llamasawait context.SaveChangesAsync()
, la ejecución del código vuelve a la función que llama! Entonces, mientras espera que todos esos registros se confirmen, su código puede continuar ejecutándose. Digamos que la función que contenía el código anterior tenía la firma depublic async Task SaveRecords(List<MyTable> saveList)
, la función de llamada podría verse así:public async Task MyCallingFunction() { Console.WriteLine("Function Starting"); Task saveTask = SaveRecords(GenerateNewRecords()); for(int i = 0; i < 1000; i++){ Console.WriteLine("Continuing to execute!"); } await saveTask; Console.Log("Function Complete"); }
Por qué tendrías una función como esta, no lo sé, pero lo que genera muestra cómo
async await
funciona. Primero repasemos lo que sucede.La ejecución ingresa
MyCallingFunction
,Function Starting
luegoSave Starting
se escribe en la consola y luegoSaveChangesAsync()
se llama a la función . En este punto, la ejecución regresaMyCallingFunction
e ingresa al bucle for escribiendo 'Continuar con la ejecución' hasta 1000 veces. CuandoSaveChangesAsync()
finaliza, la ejecución vuelve a laSaveRecords
función, escribiendoSave Complete
en la consola. Una vez que todo estéSaveRecords
completo, la ejecución continuará en el lugarMyCallingFunction
correcto donde estaba cuandoSaveChangesAsync()
terminó. ¿Confuso? Aquí hay una salida de ejemplo:O tal vez:
Esa es la belleza de
async await
su código puede continuar ejecutándose mientras espera que algo termine. En realidad, tendrías una función más como esta como tu función de llamada:public async Task MyCallingFunction() { List<Task> myTasks = new List<Task>(); myTasks.Add(SaveRecords(GenerateNewRecords())); myTasks.Add(SaveRecords2(GenerateNewRecords2())); myTasks.Add(SaveRecords3(GenerateNewRecords3())); myTasks.Add(SaveRecords4(GenerateNewRecords4())); await Task.WhenAll(myTasks.ToArray()); }
Aquí, tiene cuatro funciones diferentes de guardar registros al mismo tiempo .
MyCallingFunction
se completará mucho más rápido usandoasync await
que si lasSaveRecords
funciones individuales fueran llamadas en serie.Lo único que aún no he mencionado es la
await
palabra clave. Lo que hace esto es detener la ejecución de la función actual hastaTask
que se complete lo que está esperando. Entonces, en el caso del originalMyCallingFunction
, la líneaFunction Complete
no se escribirá en la consola hastaSaveRecords
que finalice la función.En pocas palabras, si tiene una opción para usar
async await
, debería hacerlo, ya que aumentará en gran medida el rendimiento de su aplicación.fuente
await
Sin embargo, si usa , incluso si USTED no necesita hacer nada más después de la llamada a SaveChanges, ASP dirá "aha, este hilo regresó esperando una operación asíncrona, esto significa que puedo dejar que este hilo maneje alguna otra solicitud mientras tanto ! " Esto hace que su aplicación escale horizontalmente mucho mejor.await
deSaveChangesAsync
puesto EF no admite varios ahorra al mismo tiempo. docs.microsoft.com/en-us/ef/core/saving/async Además, existe una gran ventaja al utilizar estos métodos asíncronos. Por ejemplo, puede seguir recibiendo otras solicitudes en su webApi al guardar datos o realizar muchas tareas, o mejorar la experiencia del usuario sin congelar la interfaz cuando está en una aplicación de escritorio.Mi explicación restante se basará en el siguiente fragmento de código.
using System; using System.Threading; using System.Threading.Tasks; using static System.Console; public static class Program { const int N = 20; static readonly object obj = new object(); static int counter; public static void Job(ConsoleColor color, int multiplier = 1) { for (long i = 0; i < N * multiplier; i++) { lock (obj) { counter++; ForegroundColor = color; Write($"{Thread.CurrentThread.ManagedThreadId}"); if (counter % N == 0) WriteLine(); ResetColor(); } Thread.Sleep(N); } } static async Task JobAsync() { // intentionally removed } public static async Task Main() { // intentionally removed } }
Caso 1
static async Task JobAsync() { Task t = Task.Run(() => Job(ConsoleColor.Red, 1)); Job(ConsoleColor.Green, 2); await t; Job(ConsoleColor.Blue, 1); } public static async Task Main() { Task t = JobAsync(); Job(ConsoleColor.White, 1); await t; }
Observaciones: Como la parte sincrónica (verde) de
JobAsync
giros más largos que la tareat
(rojo), la tareat
ya está completada en el punto deawait t
. Como resultado, la continuación (azul) se ejecuta en el mismo hilo que el verde. La parte sincrónica deMain
(blanco) girará después de que la verde termine de girar. Es por eso que la parte sincrónica en el método asincrónico es problemática.Caso 2
static async Task JobAsync() { Task t = Task.Run(() => Job(ConsoleColor.Red, 2)); Job(ConsoleColor.Green, 1); await t; Job(ConsoleColor.Blue, 1); } public static async Task Main() { Task t = JobAsync(); Job(ConsoleColor.White, 1); await t; }
Observaciones: Este caso es opuesto al primer caso. La parte sincrónica (verde) de los
JobAsync
giros más cortos que la tarea.t
(rojo), entonces la tareat
no se ha completado en el punto deawait t
. Como resultado, la continuación (azul) se ejecuta en el hilo diferente al verde. La parte sincrónica deMain
(blanco) todavía gira después de que la verde termina de girar.Caso 3
static async Task JobAsync() { Task t = Task.Run(() => Job(ConsoleColor.Red, 1)); await t; Job(ConsoleColor.Green, 1); Job(ConsoleColor.Blue, 1); } public static async Task Main() { Task t = JobAsync(); Job(ConsoleColor.White, 1); await t; }
Observaciones: Este caso solucionará el problema de los casos anteriores sobre la parte síncrona en método asíncrono. La tarea
t
se espera de inmediato. Como resultado, la continuación (azul) se ejecuta en el hilo diferente al verde. La parte sincrónica deMain
(blanco) girará inmediatamente en paralelo aJobAsync
.Si desea agregar otros casos, no dude en editar.
fuente
Esta declaración es incorrecta:
No es necesario agregar "await",
await
es simplemente una palabra clave conveniente en C # que le permite escribir más líneas de código después de la llamada, y esas otras líneas solo se ejecutarán después de que se complete la operación Guardar. Pero, como señaló, podría lograrlo simplemente llamando enSaveChanges
lugar deSaveChangesAsync
.Pero, fundamentalmente, una llamada asincrónica es mucho más que eso. La idea aquí es que si hay otro trabajo que pueda hacer (en el servidor) mientras la operación Guardar está en progreso, entonces debería usar
SaveChangesAsync
. No utilice "aguardar". Simplemente llameSaveChangesAsync
y luego continúe haciendo otras cosas en paralelo. Esto incluye potencialmente, en una aplicación web, devolver una respuesta al cliente incluso antes de que se haya completado el guardado. Pero, por supuesto, aún querrá verificar el resultado final de Guardar para que, en caso de que falle, pueda comunicárselo a su usuario o iniciar sesión de alguna manera.fuente