¿Alguien puede explicar qué hace la await
función?
¿Alguien puede explicar qué hace la await
función?
Respuestas:
Ellos sólo hablamos de esto en el PDC ayer!
Await se usa junto con Tasks (programación paralela) en .NET. Es una palabra clave que se introduce en la próxima versión de .NET. Más o menos le permite "pausar" la ejecución de un método para esperar a que la tarea complete la ejecución. He aquí un breve ejemplo:
//create and run a new task
Task<DataTable> dataTask = new Task<DataTable>(SomeCrazyDatabaseOperation);
//run some other code immediately after this task is started and running
ShowLoaderControl();
StartStoryboard();
//this will actually "pause" the code execution until the task completes. It doesn't lock the thread, but rather waits for the result, similar to an async callback
// please so also note, that the task needs to be started before it can be awaited. Otherwise it will never return
dataTask.Start();
DataTable table = await dataTask;
//Now we can perform operations on the Task result, as if we're executing code after the async operation completed
listBoxControl.DataContext = table;
StopStoryboard();
HideLoaderControl();
Básicamente, las palabras clave async
y le await
permiten especificar que la ejecución de un método debe detenerse en todos los usos de await
, que marcan las llamadas a métodos asincrónicos, y luego reanudar una vez que se completa la operación asincrónica. Esto le permite llamar a un método en el hilo principal de una aplicación y manejar el trabajo complejo de forma asincrónica, sin la necesidad de definir explícitamente hilos y uniones o bloquear el hilo principal de la aplicación.
Piense en ello como algo similar a una yield return
declaración en un método que produce un IEnumerable. Cuando el tiempo de ejecución yield
llegue al, básicamente guardará el estado actual del método y devolverá el valor o la referencia que se obtiene. La próxima vez que se llame a IEnumerator.MoveNext () en el objeto de retorno (que es generado internamente por el tiempo de ejecución), el estado anterior del método se restaura en la pila y la ejecución continúa con la siguiente línea después del yield return
como si nunca hubiéramos dejado el método. Sin esta palabra clave, un tipo IEnumerator debe definirse de forma personalizada para almacenar el estado y manejar las solicitudes de iteración, con métodos que pueden volverse MUY complejos.
Del mismo modo, un método marcado como async
debe tener al menos uno await
. En un await
tiempo, el tiempo de ejecución guardará el estado del hilo actual y la pila de llamadas, realizará la llamada asincrónica y volverá al ciclo de mensajes del tiempo de ejecución para manejar el siguiente mensaje y mantener la aplicación receptiva. Cuando se completa la operación asíncrona, en la siguiente oportunidad de programación, la pila de llamadas para aumentar la operación asíncrona se devuelve y continúa como si la llamada fuera síncrona.
Por lo tanto, estas dos nuevas palabras clave básicamente simplifican la codificación de procesos asincrónicos, al igual que yield return
simplificaron la generación de enumerables personalizados. Con un par de palabras clave y un poco de conocimiento previo, puede omitir todos los detalles confusos y, a menudo, propensos a errores de un patrón asincrónico tradicional. Esto será INVALUABLE en prácticamente cualquier aplicación GUI impulsada por eventos como Winforms, WPF de Silverlight.
La respuesta actualmente aceptada es engañosa.
await
no está pausando nada. En primer lugar, solo se puede usar en métodos o lambdas marcados como async
y regresando Task
o void
si no le importa que la Task
instancia se ejecute en este método.
Aquí hay una ilustración:
internal class Program
{
private static void Main(string[] args)
{
var task = DoWork();
Console.WriteLine("Task status: " + task.Status);
Console.WriteLine("Waiting for ENTER");
Console.ReadLine();
}
private static async Task DoWork()
{
Console.WriteLine("Entered DoWork(). Sleeping 3");
// imitating time consuming code
// in a real-world app this should be inside task,
// so method returns fast
Thread.Sleep(3000);
await Task.Run(() =>
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine("async task iteration " + i);
// imitating time consuming code
Thread.Sleep(1000);
}
});
Console.WriteLine("Exiting DoWork()");
}
}
Salida:
Ingresó a DoWork (). Dormir 3
iteración de tarea asíncrona 0
Estado de la tarea: WaitingForActivation
Esperando ENTER
iteración de
tarea asíncrona 1 iteración de tarea asíncrona 2
iteración de
tarea asíncrona 3 iteración de
tarea asíncrona 4 iteración de tarea
asíncrona 5 iteración de
tarea asíncrona 6 iteración de
tarea asíncrona 7 iteración de
tarea asíncrona 8 iteración de tarea asíncrona 9
Saliendo Hacer trabajo()
await
? ¿Significa que si esto se llama desde un hilo de la interfaz de usuario, bloqueará el hilo de la interfaz de usuario durante 3 segundos? La idea de este modelo es evitar hacer cosas así.
Para cualquier persona nueva en la programación asincrónica en .NET, aquí hay una analogía (totalmente falsa) en un escenario con el que puede estar más familiarizado: llamadas AJAX usando JavaScript / jQuery. Una publicación simple de jQuery AJAX se ve así:
$.post(url, values, function(data) {
// AJAX call completed, do something with returned data here
});
La razón por la que procesamos los resultados en una función de devolución de llamada es para que no bloqueemos el hilo actual mientras esperamos que regrese la llamada AJAX. Solo cuando la respuesta esté lista, se activará la devolución de llamada, liberando el hilo actual para hacer otras cosas mientras tanto.
Ahora, si JavaScript admite la await
palabra clave (que por supuesto no lo hace ( ¡todavía! )), Podría lograr lo mismo con esto:
var data = await $.post(url, values);
// AJAX call completed, do something with returned data here
Eso es mucho más limpio, pero seguro que parece que introdujimos un código de bloqueo sincrónico. Pero el compilador de JavaScript (falso) habría tomado todo después await
y lo habría conectado a una devolución de llamada, por lo que en tiempo de ejecución el segundo ejemplo se comportaría como el primero.
Puede que no parezca que le está ahorrando mucho trabajo, pero cuando se trata de cosas como el manejo de excepciones y los contextos de sincronización, el compilador en realidad está haciendo un gran trabajo pesado por usted. Para obtener más información, recomendaría las preguntas frecuentes seguidas de la serie de blogs de Stephen Cleary .
Si tuviera que implementarlo en Java, se vería así:
/**
* @author Ilya Gazman
*/
public abstract class SynchronizedTask{
private ArrayList<Runnable> listeners = new ArrayList<Runnable>();
private static final ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(6, 6, 0, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(1000));
public final void await(Runnable listener){
synchronized (this) {
listeners.add(listener);
}
}
public void excecute(){
onExcecute();
for (int i = listeners.size() - 1; i >= 0; i--) {
Runnable runnable;
synchronized (this) {
runnable = listeners.remove(i);
}
threadPoolExecutor.execute(runnable);
}
}
protected abstract void onExcecute();
}
Su aplicación lo usaría así:
public class Test{
private Job job = new Job();
public Test() {
craeteSomeJobToRunInBackground();
methode1();
methode2();
}
private void methode1(){
System.out.println("Running methode 1");
job.await(new Runnable() {
@Override
public void run() {
System.out.println("Continue to running methode 1");
}
});
}
private void methode2(){
System.out.println("Running methode 2");
}
private void craeteSomeJobToRunInBackground() {
new Thread(new Runnable() {
@Override
public void run() {
job.excecute();
}
}).start();
}
private class Job extends SynchronizedTask{
@Override
protected void onExcecute() {
try {
Thread.sleep(1000);
}
catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Job is done");
}
}
}