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 Asyncmé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
myListde 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 listamyLista 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 awaitque resuelve el patrón.Para operaciones de E / S,
awaitse 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
MyEDMy agrega sumyListaMyTable. ¡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 awaitfunciona. Primero repasemos lo que sucede.La ejecución ingresa
MyCallingFunction,Function StartingluegoSave Startingse escribe en la consola y luegoSaveChangesAsync()se llama a la función . En este punto, la ejecución regresaMyCallingFunctione ingresa al bucle for escribiendo 'Continuar con la ejecución' hasta 1000 veces. CuandoSaveChangesAsync()finaliza, la ejecución vuelve a laSaveRecordsfunción, escribiendoSave Completeen la consola. Una vez que todo estéSaveRecordscompleto, la ejecución continuará en el lugarMyCallingFunctioncorrecto donde estaba cuandoSaveChangesAsync()terminó. ¿Confuso? Aquí hay una salida de ejemplo:O tal vez:
Esa es la belleza de
async awaitsu 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 .
MyCallingFunctionse completará mucho más rápido usandoasync awaitque si lasSaveRecordsfunciones individuales fueran llamadas en serie.Lo único que aún no he mencionado es la
awaitpalabra clave. Lo que hace esto es detener la ejecución de la función actual hastaTaskque se complete lo que está esperando. Entonces, en el caso del originalMyCallingFunction, la líneaFunction Completeno se escribirá en la consola hastaSaveRecordsque 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
awaitSin 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.awaitdeSaveChangesAsyncpuesto 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
JobAsyncgiros más largos que la tareat(rojo), la tareatya 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
JobAsyncgiros más cortos que la tarea.t(rojo), entonces la tareatno 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
tse 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",
awaites 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 enSaveChangeslugar 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 llameSaveChangesAsyncy 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