c# - mvc - Cómo y cuándo usar ''async'' y ''esperar''
metodos async c# mvc (20)
A mi entender, una de las principales cosas que async
y await
hacen es hacer que el código sea fácil de escribir y leer, pero ¿se está utilizando de forma similar a los subprocesos de fondo para generar lógica de larga duración?
Actualmente estoy probando el ejemplo más básico. He añadido algunos comentarios en línea. ¿Me lo puedes aclarar?
// I don''t understand why this method must be marked as `async`.
private async void button1_Click(object sender, EventArgs e)
{
Task<int> access = DoSomethingAsync();
// task independent stuff here
// this line is reached after the 5 seconds sleep from
// DoSomethingAsync() method. Shouldn''t it be reached immediately?
int a = 1;
// from my understanding the waiting should be done here.
int x = await access;
}
async Task<int> DoSomethingAsync()
{
// is this executed on a background thread?
System.Threading.Thread.Sleep(5000);
return 1;
}
¿Los está utilizando igual a los subprocesos de fondo de generación para realizar la lógica de larga duración?
Este artículo MDSN: Programación asíncrona con async y await (C #) lo explica explícitamente:
Las palabras clave async y espera no hacen que se creen subprocesos adicionales. Los métodos asíncronos no requieren subprocesos múltiples porque un método asíncrono no se ejecuta en su propio hilo. El método se ejecuta en el contexto de sincronización actual y usa el tiempo en el hilo solo cuando el método está activo.
A mi entender, una de las principales cosas que async y espere hacen es hacer que el código sea fácil de escribir y leer.
Deben hacer que el código asíncrono sea fácil de escribir y leer, sí.
¿Es lo mismo que generar subprocesos de fondo para realizar una lógica de larga duración?
De ningún modo.
// No entiendo por qué este método debe marcarse como "asíncrono".
La palabra clave async
habilita la palabra clave await
. Por lo tanto, cualquier método que utilice await
debe marcarse como async
.
// esta línea se alcanza después de los 5 segundos de espera del método DoSomethingAsync (). ¿No debería ser alcanzado inmediatamente?
No, porque los métodos async
no se ejecutan en otro hilo de forma predeterminada.
// ¿Esto se ejecuta en un hilo de fondo?
No.
Puede encontrar útil mi introducción async
/ await
. Los documentos oficiales de MSDN también son inusualmente buenos (particularmente la sección TAP ), y el equipo de async
una excelente FAQ .
Explicación
Aquí hay un ejemplo rápido de async / await a un alto nivel. Hay muchos más detalles a considerar más allá de esto.
Nota: Task.Delay(1000)
simula hacer trabajo durante 1 segundo. Creo que es mejor pensar en esto como esperando una respuesta de un recurso externo. Debido a que nuestro código está esperando una respuesta, el sistema puede hacer que la tarea se ejecute y volver a ella una vez que haya terminado. Mientras tanto, puede hacer algún otro trabajo en ese hilo.
En el siguiente ejemplo, el primer bloque está haciendo exactamente eso. Inicia todas las tareas inmediatamente (las líneas Task.Delay
) y las pone a un lado. El código se detendrá en la línea de await a
hasta que se realice el retraso de 1 segundo antes de pasar a la línea siguiente. Como b
, c
, d
, y e
comenzaron a ejecutarse casi al mismo tiempo que a
(debido a la falta de espera), deben terminar aproximadamente al mismo tiempo en este caso.
En el ejemplo a continuación, el segundo bloque está iniciando una tarea y esperando a que termine (eso es lo que await
) antes de comenzar las tareas subsiguientes. Cada iteración de esto toma 1 segundo. La await
es pausar el programa y esperar el resultado antes de continuar. Esta es la principal diferencia entre el primer y el segundo bloque.
Ejemplo
Console.WriteLine(DateTime.Now);
// This block takes 1 second to run because all
// 5 tasks are running simultaneously
{
var a = Task.Delay(1000);
var b = Task.Delay(1000);
var c = Task.Delay(1000);
var d = Task.Delay(1000);
var e = Task.Delay(1000);
await a;
await b;
await c;
await d;
await e;
}
Console.WriteLine(DateTime.Now);
// This block takes 5 seconds to run because each "await"
// pauses the program until the task finishes
{
await Task.Delay(1000);
await Task.Delay(1000);
await Task.Delay(1000);
await Task.Delay(1000);
await Task.Delay(1000);
}
Console.WriteLine(DateTime.Now);
SALIDA:
5/24/2017 2:22:50 PM
5/24/2017 2:22:51 PM (First block took 1 second)
5/24/2017 2:22:56 PM (Second block took 5 seconds)
Información adicional sobre SynchronizationContext
Nota: aquí es donde las cosas se ponen un poco confusas para mí, así que si me equivoco en algo, corríjame y actualizaré la respuesta. Es importante tener una comprensión básica de cómo funciona esto, pero puede subsistir sin ser un experto en ello siempre y cuando nunca use ConfigureAwait(false)
, aunque supongo que perderá alguna oportunidad de optimización.
Hay un aspecto de esto que hace que el concepto de async / await sea algo más difícil de entender. Ese es el hecho de que en este ejemplo, todo esto está sucediendo en el mismo hilo (o al menos lo que parece ser el mismo hilo en lo que respecta a su SynchronizationContext). De forma predeterminada, await
restaurará el contexto de sincronización del subproceso original en el que se estaba ejecutando. Por ejemplo, en ASP.NET tienes un HttpContext que está vinculado a un hilo cuando entra una solicitud. Este contexto contiene cosas específicas de la solicitud Http original, como el objeto de solicitud original que tiene cosas como el idioma, la dirección IP, los encabezados, etc. Si cambia los hilos a mitad del proceso de procesamiento, podría terminar tratando de extraer información de este objeto en un HttpContext diferente, lo que podría ser desastroso. Si sabe que no utilizará el contexto para nada, puede elegir "no preocuparse" por ello. Básicamente, esto permite que su código se ejecute en un subproceso separado sin traer el contexto.
¿Cómo lograr esto? Por defecto, la await a;
el código en realidad supone que QUIERES capturar y restaurar el contexto:
await a; //Same as the line below
await a.ConfigureAwait(true);
Si desea permitir que el código principal continúe en un nuevo hilo sin el contexto original, simplemente use falso en lugar de verdadero para que sepa que no es necesario restaurar el contexto.
await a.ConfigureAwait(false);
Una vez que el programa haya terminado de pausarse, continuará potencialmente en un subproceso completamente diferente con un contexto diferente. Aquí es de donde vendría la mejora del rendimiento: podría continuar en cualquier subproceso disponible sin tener que restaurar el contexto original con el que comenzó.
¿Esto es confuso? ¡Demonios si! ¿Puedes averiguarlo? ¡Probablemente! Una vez que haya comprendido los conceptos, continúe con las explicaciones de Stephen Cleary, que tienden a estar más orientadas hacia alguien con un conocimiento técnico de asíncrono / espera.
Async / Await
Acctually Async / Await son un par de palabras clave que son solo azúcar sintáctica para crear una devolución de llamada de una tarea asíncrona.
Tomemos como ejemplo esta operación:
public static void DoSomeWork()
{
var task = Task.Run(() =>
{
// [RUNS ON WORKER THREAD]
// IS NOT bubbling up due to the different threads
throw new Exception();
Thread.Sleep(2000);
return "Hello";
});
// This is the callback
task.ContinueWith((t) => {
// -> Exception is swallowed silently
Console.WriteLine("Completed");
// [RUNS ON WORKER THREAD]
});
}
Este método tiene desventajas servales. Los errores no se transmiten y es extremadamente difícil de leer. Pero Async y Await vienen a ayudarnos:
public async static void DoSomeWork()
{
var result = await Task.Run(() =>
{
// [RUNS ON WORKER THREAD]
// IS bubbling up
throw new Exception();
Thread.Sleep(2000);
return "Hello";
});
// every thing below is a callback
// (including the calling methods)
Console.WriteLine("Completed");
}
Las llamadas de espera tienen que estar en métodos asíncronos. Esto tiene algunas ventajas:
- Devuelve el resultado de la tarea.
- crea automáticamente una devolución de llamada
- comprueba los errores y los deja crecer en la pila de llamadas (solo hasta las llamadas no esperadas en la pila de llamadas)
- espera el resultado
- libera el hilo principal
- ejecuta la devolución de llamada en el hilo principal
- utiliza un subproceso de trabajo del conjunto de subprocesos para la tarea
- hace que el código sea fácil de leer
- y mucho más
NOTA : Async y Await se utilizan con llamadas asíncronas para no realizarlas. Tienes que usar la biblioteca de tareas para esto, como Task.Run ().
Aquí hay una comparación entre esperar y ninguna espera soluciones.
Esta es la solución ninguno asincrónica:
public static long DoTask()
{
stopWatch.Reset();
stopWatch.Start();
// [RUNS ON MAIN THREAD]
var task = Task.Run(() => {
Thread.Sleep(2000);
// [RUNS ON WORKER THREAD]
});
Thread.Sleep(1000);
// goes directly further
// WITHOUT waiting until the task is finished
// [RUNS ON MAIN THREAD]
stopWatch.Stop();
// 50 milliseconds
return stopWatch.ElapsedMilliseconds;
}
Este es el método asíncrono:
public async static Task<long> DoAwaitTask()
{
stopWatch.Reset();
stopWatch.Start();
// [RUNS ON MAIN THREAD]
await Task.Run(() => {
Thread.Sleep(2000);
// [RUNS ON WORKER THREAD]
});
// Waits until task is finished
// [RUNS ON MAIN THREAD]
stopWatch.Stop();
// 2050 milliseconds
return stopWatch.ElapsedMilliseconds;
}
Accuually puede llamar a un método asíncrono sin la palabra clave await, pero esto significa que cualquier excepción aquí se ingiere en modo de liberación:
public static Stopwatch stopWatch { get; } = new Stopwatch();
static void Main(string[] args)
{
Console.WriteLine("DoAwaitTask: " + DoAwaitTask().Result + " ms");
// 2050 (2000 more because of the await)
Console.WriteLine("DoTask: " + DoTask() + " ms");
// 50
Console.ReadKey();
}
Async y Await no están diseñados para la computación paralela. Se utilizan para no bloquear su hilo principal. Si se trata de asp.net o de las aplicaciones de windows. Bloquear el hilo principal debido a una llamada de red es algo malo. Si haces esto, tu aplicación dejará de responder o podría fallar.
Echa un vistazo a ms docs para obtener algunos ejemplos.
Espero que esto ayude;
Además de las otras respuestas, eche un vistazo a await (Referencia de C #)
Y más específicamente en el ejemplo incluido, explica un poco su situación.
El siguiente ejemplo de Windows Forms ilustra el uso de await en un método asíncrono, WaitAsynchronouslyAsync. Contraste el comportamiento de ese método con el comportamiento de WaitSynchronously. Sin un operador de espera aplicado a una tarea, WaitSynchronically se ejecuta de forma síncrona a pesar del uso del modificador asíncrono en su definición y una llamada a Thread.Sleep en su cuerpo.
private async void button1_Click(object sender, EventArgs e)
{
// Call the method that runs asynchronously.
string result = await WaitAsynchronouslyAsync();
// Call the method that runs synchronously.
//string result = await WaitSynchronously ();
// Display the result.
textBox1.Text += result;
}
// The following method runs asynchronously. The UI thread is not
// blocked during the delay. You can move or resize the Form1 window
// while Task.Delay is running.
public async Task<string> WaitAsynchronouslyAsync()
{
await Task.Delay(10000);
return "Finished";
}
// The following method runs synchronously, despite the use of async.
// You cannot move or resize the Form1 window while Thread.Sleep
// is running because the UI thread is blocked.
public async Task<string> WaitSynchronously()
{
// Add a using directive for System.Threading.
Thread.Sleep(10000);
return "Finished";
}
Aquí hay un programa de consola rápida para que quede claro para aquellos que lo siguen. El método "TaskToDo" es su método de ejecución larga que desea hacer asíncrono. Hacer que se ejecute Async se realiza mediante el método TestAsync. El método de bucles de prueba solo se ejecuta a través de las tareas "Tareas pendientes" y las ejecuta de forma asíncrona. Puede ver eso en los resultados porque no se completan en el mismo orden de ejecución a ejecución; están informando al subproceso de la interfaz de usuario de la consola cuando se completan. Simplista, pero creo que los ejemplos simplistas resaltan el núcleo del patrón mejor que los ejemplos más complejos:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace TestingAsync
{
class Program
{
static void Main(string[] args)
{
TestLoops();
Console.Read();
}
private static async void TestLoops()
{
for (int i = 0; i < 100; i++)
{
await TestAsync(i);
}
}
private static Task TestAsync(int i)
{
return Task.Run(() => TaskToDo(i));
}
private async static void TaskToDo(int i)
{
await Task.Delay(10);
Console.WriteLine(i);
}
}
}
Creo que has elegido un mal ejemplo con System.Threading.Thread.Sleep
El punto de una tarea async
es dejar que se ejecute en segundo plano sin bloquear el hilo principal, como hacer un DownloadFileAsync
System.Threading.Thread.Sleep
no es algo que se esté "haciendo", solo duerme y, por lo tanto, se llega a la siguiente línea después de 5 segundos ...
Lea este artículo, creo que es una gran explicación de async
y await
concepto: http://msdn.microsoft.com/en-us/library/vstudio/hh191443.aspx
Cuando se usa async
y await
el compilador genera una máquina de estado en segundo plano.
Aquí hay un ejemplo en el que espero poder explicar algunos de los detalles de alto nivel que están ocurriendo:
public async Task MyMethodAsync()
{
Task<int> longRunningTask = LongRunningOperationAsync();
// independent work which doesn''t need the result of LongRunningOperationAsync can be done here
//and now we call await on the task
int result = await longRunningTask;
//use the result
Console.WriteLine(result);
}
public async Task<int> LongRunningOperationAsync() // assume we return an int from this long running operation
{
await Task.Delay(1000); // 1 second delay
return 1;
}
OK, entonces, ¿qué pasa aquí?
Task<int> longRunningTask = LongRunningOperationAsync();
comienza a ejecutarLongRunningOperation
El trabajo independiente se realiza en Asumamos el hilo principal (Thread ID = 1) y luego
await longRunningTask
se llegue aawait longRunningTask
.Ahora, si
longRunningTask
no ha finalizado y aún se está ejecutando,MyMethodAsync()
volverá a su método de llamada, por lo que el hilo principal no se bloqueará. CuandolongRunningTask
, un subproceso de ThreadPool (puede ser cualquier subproceso) volverá aMyMethodAsync()
en su contexto anterior y continuará la ejecución (en este caso, imprimiendo el resultado en la consola).
Un segundo caso sería que longRunningTask
ya ha finalizado su ejecución y el resultado está disponible. Cuando await longRunningTask
la await longRunningTask
, ya tenemos el resultado, por lo que el código continuará ejecutándose en el mismo hilo. (en este caso imprimiendo resultado a consola). Por supuesto, este no es el caso del ejemplo anterior, donde hay un Task.Delay(1000)
involucrado.
En el siguiente código, el método HttpClient GetByteArrayAsync devuelve una tarea, getContentsTask. La tarea es una promesa de producir la matriz de bytes real cuando la tarea está completa. El operador await se aplica a getContentsTask para suspender la ejecución en SumPageSizesAsync hasta que se complete getContentsTask. Mientras tanto, el control se devuelve a la persona que llama en SumPageSizesAsync. Cuando finaliza getContentsTask, la expresión de espera se evalúa como una matriz de bytes.
private async Task SumPageSizesAsync()
{
// To use the HttpClient type in desktop apps, you must include a using directive and add a
// reference for the System.Net.Http namespace.
HttpClient client = new HttpClient();
// . . .
Task<byte[]> getContentsTask = client.GetByteArrayAsync(url);
byte[] urlContents = await getContentsTask;
// Equivalently, now that you see how it works, you can write the same thing in a single line.
//byte[] urlContents = await client.GetByteArrayAsync(url);
// . . .
}
Esta respuesta tiene como objetivo proporcionar alguna información específica de ASP.NET.
Al utilizar async / await en el controlador MVC, es posible aumentar la utilización del conjunto de subprocesos y lograr un rendimiento mucho mejor, como se explica en el siguiente artículo:
http://www.asp.net/mvc/tutorials/mvc-4/using-asynchronous-methods-in-aspnet-mvc-4
En las aplicaciones web que ven una gran cantidad de solicitudes simultáneas en el inicio o que tienen una carga repentina (donde la concurrencia aumenta repentinamente), hacer que estas llamadas de servicio web sean asíncronas aumentará la capacidad de respuesta de su aplicación. Una solicitud asíncrona toma la misma cantidad de tiempo para procesarse como una solicitud síncrona. Por ejemplo, si una solicitud realiza una llamada de servicio web que requiere dos segundos para completarse, la solicitud demora dos segundos si se realiza de forma síncrona o asíncrona. Sin embargo, durante una llamada asíncrona, un subproceso no está bloqueado para responder a otras solicitudes mientras espera a que se complete la primera solicitud. Por lo tanto, las solicitudes asíncronas evitan la puesta en cola de solicitudes y el crecimiento del grupo de subprocesos cuando hay muchas solicitudes simultáneas que invocan operaciones de larga ejecución.
Mostrando las explicaciones anteriores en acción en un programa de consola simple -
class Program
{
static void Main(string[] args)
{
TestAsyncAwaitMethods();
Console.WriteLine("Press any key to exit...");
Console.ReadLine();
}
public async static void TestAsyncAwaitMethods()
{
await LongRunningMethod();
}
public static async Task<int> LongRunningMethod()
{
Console.WriteLine("Starting Long Running method...");
await Task.Delay(5000);
Console.WriteLine("End Long Running method...");
return 1;
}
}
Y la salida es:
Starting Long Running method...
Press any key to exit...
End Long Running method...
Así,
- Main inicia el método de ejecución larga a través de TestAsyncAwaitMethods. Eso regresa inmediatamente sin detener el hilo actual y vemos de inmediato el mensaje ''Presione cualquier tecla para salir''
- Todo este tiempo, el LongRunningMethod se ejecuta en segundo plano. Una vez completado, otro hilo de Threadpool recoge este contexto y muestra el mensaje final
Por lo tanto, no se bloquea el hilo.
Para ser honesto, sigo pensando que la mejor explicación es la del futuro y las promesas en la Wikipedia: http://en.wikipedia.org/wiki/Futures_and_promises
La idea básica es que usted tiene un grupo separado de hilos que ejecutan tareas de forma asíncrona. Cuando se usa. Sin embargo, el objeto hace la promesa de que ejecutará la operación en algún momento y le dará el resultado cuando lo solicite. Esto significa que se bloqueará cuando solicite el resultado y no haya terminado, pero de lo contrario se ejecutará en el grupo de subprocesos.
Desde allí puede optimizar cosas: algunas operaciones pueden implementarse de forma asíncrona y pueden optimizar cosas como la E / S de archivos y la comunicación de red agrupando las solicitudes subsiguientes y / o reordenándolas. No estoy seguro de si esto ya está en el marco de tareas de Microsoft, pero si no lo es, sería una de las primeras cosas que agregaría.
De hecho, puede implementar la clasificación del patrón futuro con los rendimientos en C # 4.0. Si desea saber cómo funciona exactamente, puedo recomendar este enlace que hace un trabajo decente: http://code.google.com/p/fracture/source/browse/trunk/Squared/TaskLib/ . Sin embargo, si comienza a jugar con él, notará que realmente necesita soporte de idioma si desea hacer todas las cosas geniales, que es exactamente lo que hizo Microsoft.
Según entiendo también, debería agregarse un tercer término a la mezcla: Task
.
Async
es solo un calificador que usted pone en su método para decir que es un método asíncrono.
Task
es el retorno de la función async
. Se ejecuta de forma asíncrona.
Usted await
una tarea. Cuando la ejecución del código llega a esta línea, el control salta al llamante de la función original que lo rodea.
Si, en cambio, asigna el retorno de una función async
(es decir, Task
) a una variable, cuando la ejecución del código llega a esta línea, simplemente continúa más allá de esa línea en la función circundante mientras la Task
ejecuta de forma asíncrona.
Todas las respuestas aquí usan Task.Delay () o alguna otra función asíncrona integrada. Pero aquí está mi ejemplo que no usa ninguna de esas funciones asíncronas:
// Starts counting to a large numbewr and then immediately displays message "i''m counting...".
// Then it waits for task to finish and displays "finished, press any key".
static void asyncTest ()
{
Console.WriteLine("Started asyncTest()");
Task<long> task = asyncTest_count();
Console.WriteLine("Started counting, please wait...");
task.Wait(); // if you comment this line you will see that message "Finished counting" will be displayed before we actually finished counting.
//Console.WriteLine("Finished counting to " + task.Result.ToString()); // using task.Result seems to also call task.Wait().
Console.WriteLine("Finished counting.");
Console.WriteLine("Press any key to exit program.");
Console.ReadLine();
}
static async Task<long> asyncTest_count()
{
long k = 0;
Console.WriteLine("Started asyncTest_count()");
await Task.Run(() =>
{
long countTo = 100000000;
int prevPercentDone = -1;
for (long i = 0; i <= countTo; i++)
{
int percentDone = (int)(100 * (i / (double)countTo));
if (percentDone != prevPercentDone)
{
prevPercentDone = percentDone;
Console.Write(percentDone.ToString() + "% ");
}
k = i;
}
});
Console.WriteLine("");
Console.WriteLine("Finished asyncTest_count()");
return k;
}
Ver este fiddle https://dotnetfiddle.net/VhZdLU (y mejorarlo si es posible) para ejecutar una aplicación de consola simple que muestra los usos de Task, Task.WaitAll (), async y espera operadores en el mismo programa.
Este violín debe despejar su concepto de ciclo de ejecución.
Aquí está el código de ejemplo
using System;
using System.Threading.Tasks;
public class Program
{
public static void Main()
{
var a = MyMethodAsync(); //Task started for Execution and immediately goes to Line 19 of the code. Cursor will come back as soon as await operator is met
Console.WriteLine("Cursor Moved to Next Line Without Waiting for MyMethodAsync() completion");
Console.WriteLine("Now Waiting for Task to be Finished");
Task.WaitAll(a); //Now Waiting
Console.WriteLine("Exiting CommandLine");
}
public static async Task MyMethodAsync()
{
Task<int> longRunningTask = LongRunningOperation();
// independent work which doesn''t need the result of LongRunningOperationAsync can be done here
Console.WriteLine("Independent Works of now executes in MyMethodAsync()");
//and now we call await on the task
int result = await longRunningTask;
//use the result
Console.WriteLine("Result of LongRunningOperation() is " + result);
}
public static async Task<int> LongRunningOperation() // assume we return an int from this long running operation
{
Console.WriteLine("LongRunningOperation() Started");
await Task.Delay(2000); // 2 second delay
Console.WriteLine("LongRunningOperation() Finished after 2 Seconds");
return 1;
}
}
Para el aprendizaje más rápido ..
Entender el flujo de ejecución del método (con un diagrama): 3 minutos
Pregunta de introspección (sake de aprendizaje): 1 min.
Obtener rápidamente a través de la sintaxis de azúcar: 5 minutos
Comparte la confusión de un desarrollador: 5 minutos.
Problema: Cambie rápidamente una implementación real del código normal a un código asíncrono: 2 minutos
¿A dónde seguir?
Entender el flujo de ejecución del método (con un diagrama): 3 minutos
En esta imagen, solo enfócate en # 6
En el paso # 6: AccessTheWebAsync () se ha quedado sin trabajo que puede hacer sin un resultado de getStringTask. Por lo tanto, AccessTheWebAsync utiliza un operador de espera para suspender su progreso y devolver el control (rendimiento) a la persona que llama. AccessTheWebAsync devuelve una Tarea (del valor de retorno de cadena) al llamante. La tarea representa una promesa de producir un resultado de cadena. ¿Pero cuándo devolverá la llamada? una segunda llamada de nuevo?
El llamador de AccessTheWebAsync () no hizo nada más que esperar (podría haber hecho algunas tareas internas y luego haber esperado si era necesario). De modo que la persona que llama está esperando AccessTheWebAsync, y AccessTheWebAsync está esperando GetStringAsync en este momento.
Recuerde, el método ya se devolvió, no se puede devolver de nuevo (no hay segunda vez). Entonces, ¿cómo sabrá la persona que llama? ¡Se trata de tareas! La tarea fue devuelta. La tarea fue esperada (no método, no valor). El valor se establecerá en la tarea. El estado de la tarea se establecerá para completar. La persona que llama simplemente supervisa la tarea. Lecturas adicionales para más adelante here .
Pregunta introspección para aprender sake: 1 min.
Ajustemos un poco la pregunta:
¿Cómo y cuándo usar
async
yawait
Tasks
?
Porque el aprendizaje Task
cubre automáticamente los otros 2. Por el bien de aprender al menos. Por supuesto, esta es una respuesta a su pregunta sobre async
y le await
.
Obtener rápidamente a través de la sintaxis de azúcar: 5 minutos
Antes de la conversión
internal static int Method(int arg0, int arg1) { int result = arg0 + arg1; IO(); // Do some long running IO. return result; }
Otro método de tareas para llamar al método anterior.
internal static Task<int> MethodTask(int arg0, int arg1) { Task<int> task = new Task<int>(() => Method(arg0, arg1)); task.Start(); // Hot task (started task) should always be returned. return task; }
¿Mencionamos esperar o asincronizar? No. Llame al método anterior y obtendrá una tarea. Que se puede controlar. Ya sabes lo que devuelve la tarea ... un entero.
Llamar a una tarea es un poco complicado. Llamemos a MethodTask ()
internal static async Task<int> MethodAsync(int arg0, int arg1) { int result = await HelperMethods.MethodTask(arg0, arg1); return result; }
Estamos "esperando" la tarea por terminar. De ahí la await
. Como usamos await, debemos usar async (obligatorio) y MethodAsync con ''Async'' como prefijo (estándar de codificación). Más lecturas para más adelante here
Comparte la confusión de un desarrollador: 5 minutos.
¡Un desarrollador ha cometido un error al no implementar Task
pero aún funciona! Trate de entender la pregunta y solo la respuesta aceptada que se proporciona aquí . Espero que hayas leído y entendido completamente. Del mismo modo, en nuestro ejemplo, llamar a un MethodAsync()
ya creado es mucho más fácil que implementar ese método con una Task
( MethodTask()
). A la mayoría de los desarrolladores les resulta difícil familiarizarse con las Tasks
al convertir un código a uno asíncrono.
Sugerencia: intente encontrar una implementación Async existente (como MethodAsync
o ToListAsync
) para externalizar la dificultad. Por lo tanto, solo tenemos que lidiar con Async y esperar (lo que es fácil y bastante similar al código normal)
Problema: Cambie rápidamente una implementación en el mundo real del código normal a una operación asíncrona: 2 minutos
La línea de código que se muestra a continuación en la capa de datos comenzó a romperse (muchos lugares). Porque actualizamos parte de nuestro código de .Net framework 4.2 a .Net core. ¡Tuvimos que arreglar esto en 1 hora en toda la aplicación!
var myContract = query.Where(c => c.ContractID == _contractID).First();
¡pan comido!
- EntityFrameWork nuget (tiene QueryableExtensions)
- espacio de nombres = Microsoft.EntityFrameworkCore
El código se cambió así
var myContract = await query.Where(c => c.ContractID == _contractID).FirstAsync();
La firma del método cambió de
Contract GetContract(int contractnumber)
a
async Task<Contract> GetContractAsync(int contractnumber)
El método de llamada también se vio afectado:
GetContractAsync(123456);
fue llamado comoGetContractAsync(123456).Result;
¡Lo cambiamos en todas partes en 30 minutos!
¡Pero el arquitecto nos dijo que no usáramos la biblioteca EntityFrameWork solo para esto! oops ¡drama! Luego hicimos una implementación de tareas personalizada. Que sabes cómo. ¡Todavía fácil!
¿A dónde seguir? Hay un maravilloso video rápido que podríamos ver sobre la conversión de llamadas síncronas a asíncronas en ASP.Net Core , porque esa es probablemente la dirección que se tomaría después de leer esto.
A continuación se muestra el código que lee el archivo de Excel abriendo el diálogo y luego usa async y espera para ejecutar de forma asíncrona el código que lee una por una línea de excel y se enlaza a la cuadrícula
namespace EmailBillingRates
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
lblProcessing.Text = "";
}
private async void btnReadExcel_Click(object sender, EventArgs e)
{
string filename = OpenFileDialog();
Microsoft.Office.Interop.Excel.Application xlApp = new Microsoft.Office.Interop.Excel.Application();
Microsoft.Office.Interop.Excel.Workbook xlWorkbook = xlApp.Workbooks.Open(filename);
Microsoft.Office.Interop.Excel._Worksheet xlWorksheet = xlWorkbook.Sheets[1];
Microsoft.Office.Interop.Excel.Range xlRange = xlWorksheet.UsedRange;
try
{
Task<int> longRunningTask = BindGrid(xlRange);
int result = await longRunningTask;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message.ToString());
}
finally
{
//cleanup
// GC.Collect();
//GC.WaitForPendingFinalizers();
//rule of thumb for releasing com objects:
// never use two dots, all COM objects must be referenced and released individually
// ex: [somthing].[something].[something] is bad
//release com objects to fully kill excel process from running in the background
Marshal.ReleaseComObject(xlRange);
Marshal.ReleaseComObject(xlWorksheet);
//close and release
xlWorkbook.Close();
Marshal.ReleaseComObject(xlWorkbook);
//quit and release
xlApp.Quit();
Marshal.ReleaseComObject(xlApp);
}
}
private void btnSendEmail_Click(object sender, EventArgs e)
{
}
private string OpenFileDialog()
{
string filename = "";
OpenFileDialog fdlg = new OpenFileDialog();
fdlg.Title = "Excel File Dialog";
fdlg.InitialDirectory = @"c:/";
fdlg.Filter = "All files (*.*)|*.*|All files (*.*)|*.*";
fdlg.FilterIndex = 2;
fdlg.RestoreDirectory = true;
if (fdlg.ShowDialog() == DialogResult.OK)
{
filename = fdlg.FileName;
}
return filename;
}
private async Task<int> BindGrid(Microsoft.Office.Interop.Excel.Range xlRange)
{
lblProcessing.Text = "Processing File.. Please wait";
int rowCount = xlRange.Rows.Count;
int colCount = xlRange.Columns.Count;
// dt.Column = colCount;
dataGridView1.ColumnCount = colCount;
dataGridView1.RowCount = rowCount;
for (int i = 1; i <= rowCount; i++)
{
for (int j = 1; j <= colCount; j++)
{
//write the value to the Grid
if (xlRange.Cells[i, j] != null && xlRange.Cells[i, j].Value2 != null)
{
await Task.Delay(1);
dataGridView1.Rows[i - 1].Cells[j - 1].Value = xlRange.Cells[i, j].Value2.ToString();
}
}
}
lblProcessing.Text = "";
return 0;
}
}
internal class async
{
}
} `
En un nivel más alto:
1) La palabra clave Async habilita la espera y eso es todo lo que hace. La palabra clave asíncrona no ejecuta el método en un hilo separado. El principio del método f async se ejecuta de forma síncrona hasta que llega a una tarea que lleva mucho tiempo.
2) Puede esperar en un método que devuelve Tarea o Tarea de tipo T. No puede esperar en método anónimo asíncrono.
3) Los momentos en que se encuentra el hilo principal en una tarea que consume mucho tiempo o cuando se inicia el trabajo real, el hilo principal vuelve al interlocutor del método actual.
4) Si el hilo principal ve espera en una tarea que aún se está ejecutando, no la espera y regresa al interlocutor del método actual. De esta manera, la aplicación sigue siendo receptiva.
5) Esperar en la tarea de procesamiento, ahora se ejecutará en un subproceso separado del grupo de subprocesos.
6) Cuando se complete esta tarea de espera, todo el código siguiente se ejecutará por el hilo separado
A continuación se muestra el código de ejemplo. Ejecutalo y verifica el id del hilo
using System;
using System.Threading;
using System.Threading.Tasks;
namespace AsyncAwaitDemo
{
class Program
{
public static async void AsynchronousOperation()
{
Console.WriteLine("Inside AsynchronousOperation Before AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
//Task<int> _task = AsyncMethod();
int count = await AsyncMethod();
Console.WriteLine("Inside AsynchronousOperation After AsyncMethod Before Await, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
//int count = await _task;
Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await Before DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
DependentMethod(count);
Console.WriteLine("Inside AsynchronousOperation After AsyncMethod After Await After DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
}
public static async Task<int> AsyncMethod()
{
Console.WriteLine("Inside AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
int count = 0;
await Task.Run(() =>
{
Console.WriteLine("Executing a long running task which takes 10 seconds to complete, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
Thread.Sleep(20000);
count = 10;
});
Console.WriteLine("Completed AsyncMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
return count;
}
public static void DependentMethod(int count)
{
Console.WriteLine("Inside DependentMethod, Thread Id: " + Thread.CurrentThread.ManagedThreadId + ". Total count is " + count);
}
static void Main(string[] args)
{
Console.WriteLine("Started Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
AsynchronousOperation();
Console.WriteLine("Completed Main method, Thread Id: " + Thread.CurrentThread.ManagedThreadId);
Console.ReadKey();
}
}
}
Las respuestas aquí son útiles como una guía general sobre await / async. También contienen algunos detalles sobre cómo se cablea await / async. Me gustaría compartir con usted alguna experiencia práctica que debería conocer antes de usar este patrón de diseño.
El término "esperar" es literal, por lo que cualquier subproceso en el que lo llames esperará el resultado del método antes de continuar. En el hilo de primer plano , esto es un desastre . El subproceso en primer plano conlleva la carga de construir su aplicación, incluidas vistas, modelos de vistas, animaciones iniciales y todo lo demás que haya atado con esos elementos. Entonces, cuando esperas el hilo de primer plano, detienes la aplicación. El usuario espera y espera cuando nada parece suceder. Esto proporciona una experiencia de usuario negativa.
Ciertamente puede esperar un hilo de fondo usando una variedad de medios:
Device.BeginInvokeOnMainThread(async () => { await AnyAwaitableMethod(); });
// Notice that we do not await the following call,
// as that would tie it to the foreground thread.
try
{
Task.Run(async () => { await AnyAwaitableMethod(); });
}
catch
{}
El código completo para estas observaciones se encuentra en https://github.com/marcusts/xamarin-forms-annoyances . Vea la solución llamada AwaitAsyncAntipattern.sln.
El sitio de GitHub también proporciona enlaces a una discusión más detallada sobre este tema.
public static void Main(string[] args)
{
string result = DownloadContentAsync().Result;
Console.ReadKey();
}
// You use the async keyword to mark a method for asynchronous operations.
// The "async" modifier simply starts synchronously the current thread.
// What it does is enable the method to be split into multiple pieces.
// The boundaries of these pieces are marked with the await keyword.
public static async Task<string> DownloadContentAsync()// By convention, the method name ends with "Async
{
using (HttpClient client = new HttpClient())
{
// When you use the await keyword, the compiler generates the code that checks if the asynchronous operation is finished.
// If it is already finished, the method continues to run synchronously.
// If not completed, the state machine will connect a continuation method that must be executed WHEN the Task is completed.
// Http request example.
// (In this example I can set the milliseconds after "sleep=")
String result = await client.GetStringAsync("http://httpstat.us/200?sleep=1000");
Console.WriteLine(result);
// After completing the result response, the state machine will continue to synchronously execute the other processes.
return result;
}
}