¿Qué hace la nueva característica de espera de C #? [cerrado]

83

¿Alguien puede explicar qué hace la awaitfunción?

Chris Nicol
fuente
1
Véase también Programación asincrónica con Async y Await
Robert Harvey
Buenos ejemplos también en dotnetperls.com/async .
Miljen Mikic
No creo que esta pregunta sea demasiado amplia ni deba cerrarse. Se pregunta qué significa una palabra clave. (¿Una versión anterior era diferente de alguna manera?)
Panzercrisis

Respuestas:

62

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();
RTigger
fuente
2
¿Cuándo es la forma C # de promesas: en.wikipedia.org/wiki/Futures_and_promises
12
Suena muy parecido a Thread.Join ().
Steve Guidi
10
Me recuerda a COMEFROM
Joel Spolsky
20
En aras de la completitud, agreguemos que la pieza de código anterior debe estar envuelta en un método que esté adornado con una palabra clave asíncrona. Este método regresará inmediatamente tan pronto como se encuentre la primera palabra clave de espera.
Przemek
14
En sus palabras: le permite "pausar" el método, pero debe tenerse en cuenta que no pausa ni bloquea el hilo.
Matt Crinklaw-Vogt
47

Básicamente, las palabras clave asyncy le awaitpermiten 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 returndeclaración en un método que produce un IEnumerable. Cuando el tiempo de ejecución yieldllegue 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 returncomo 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 asyncdebe tener al menos uno await. En un awaittiempo, 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 returnsimplificaron 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.

KeithS
fuente
31

La respuesta actualmente aceptada es engañosa. awaitno está pausando nada. En primer lugar, solo se puede usar en métodos o lambdas marcados como asyncy regresando Tasko voidsi no le importa que la Taskinstancia 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()

Anri
fuente
1
¿También sabe que bloqueará a la persona que llama durante 3 segundos antes de que le dé la tarea en la que pueden 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í.
Servicio
1
@Servy sí, ese era el punto. Para mostrar todas las etapas de ejecución. Este no es un ejemplo del mundo real.
Anri
7
@Servy, ¿me estás trolleando?
Anri
2
No Estoy tratando de ayudarlo a mejorar su respuesta.
Servicio
2
@ Anri ... Realmente aprecio tus esfuerzos aquí. ¡¡Muchas gracias!!
Praveen Prajapati
11

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 awaitpalabra 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 awaity 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 .

Todd Menier
fuente
Siguiendo con esta analogía falsa (que me ayudó mucho por cierto, ¡así que gracias!), ¿Qué quieres decir con "todo después"? ¿Todo dentro del alcance de la misma función (método) solamente? ¿O todo lo que le sigue, como cualquier cosa que pudiera haberse agregado a la pila de llamadas?
2
"Todo después" = el resto del método. El compilador reescribe efectivamente el resto del método como una devolución de llamada y el control regresa inmediatamente al llamador del método actual.
Todd Menier
1
Brilliant Todd, gracias de nuevo por tu explicación. También es útil para otros, estoy seguro.
-2

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");
        }
    }
}
Ilya Gazman
fuente