sintaxis - ¿Cuál es la nueva función de espera de C#?
tipos de datos en c# (5)
¿Alguien puede explicar lo que hace la función de await
?
¡Acaban de hablar de esto en PDC ayer!
Await se usa junto con Tareas (programación paralela) en .NET. Es una palabra clave que se presenta 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. Aquí hay 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 await
permiten especificar que la ejecución de un método debe detenerse en todos los usos de await
, que marcan llamadas a métodos asíncronos y luego se reanuda 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 subprocesos y uniones o bloquear el hilo principal de la aplicación.
Piense que es algo similar a una declaración de yield return
en un método que produce un IEnumerable. Cuando el tiempo de ejecución alcanza el yield
, básicamente salvará el estado actual del método y devolverá el valor o referencia que se obtiene. La próxima vez que se llame a IEnumerator.MoveNext () en el objeto de retorno (que se genera 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 dejó el método. Sin esta palabra clave, un tipo de IEnumerator debe ser personalizado para almacenar el estado y manejar las solicitudes de iteración, con métodos que pueden llegar a ser MUY complejos de hecho.
Del mismo modo, un método marcado como async
debe tener al menos uno en await
. En await
, el tiempo de ejecución guardará el estado del hilo actual y la pila de llamadas, realizará la llamada asíncrona y se relajará nuevamente al bucle de mensajes del tiempo de ejecución para manejar el siguiente mensaje y mantener la aplicación en buen estado. Cuando se completa la operación asincrónica, en la próxima oportunidad de programación, la pila de llamadas para subir a la operación asincrónica se vuelve a presionar y continúa como si la llamada fuera sincrónica.
Por lo tanto, estas dos nuevas palabras clave simplifican básicamente la codificación de procesos asíncronos, al igual que el yield return
simplificó 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 propensos a errores de un patrón asincrónico tradicional. Esto será INVALUABLE en prácticamente cualquier aplicación GUI basada en eventos como Winforms, WPF de Silverlight.
La respuesta actualmente aceptada es engañosa. await
no está pausando nada. En primer lugar, se puede usar solo en métodos o lambdas marcados como async
y devolviendo Task
o void
si no te importa que Task
instancia de la Task
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:
Entró DoWork (). Dormir 3
iteración de la tarea asincrónica 0
Estado de la tarea: WaitingForActivation
Esperando ENTER
iteración de tareas asincrónicas 1
iteración de la tarea asincrónica 2
iteración de la tarea asincrónica 3
iteración de tareas asíncronas 4
iteración de tareas asincrónicas 5
iteración de tareas asincrónicas 6
iteración de tareas asíncronas 7
iteración de la tarea asincrónica 8
iteración de la tarea asincrónica 9
Saliendo de DoWork ()
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 simple publicación jQuery AJAX se ve así:
$.post(url, values, function(data) {
// AJAX call completed, do something with returned data here
});
El motivo por el que procesamos los resultados en una función de devolución de llamada es para no bloquear el hilo actual mientras esperamos que vuelva la llamada AJAX. Solo cuando la respuesta esté lista se activará la devolución de llamada, liberando el hilo actual para que haga otras cosas mientras tanto.
Ahora, si JavaScript admite la palabra clave await
(que, por supuesto, no ( yet! )), 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 parece que introdujimos un código de bloqueo sincrónico. Pero el compilador de JavaScript (falso) habría tomado todo después de 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 igual que el primero.
Puede que no parezca que le está ahorrando mucho trabajo, pero cuando se trata de situaciones como el manejo de excepciones y los contextos de sincronización, el compilador realmente está haciendo mucho trabajo pesado por usted. Para obtener más información, recomendaría las FAQs seguidas por la serie de blogs de Stephen Cleary .
Si tuviera que implementarlo en Java, se vería algo como esto:
/**
* @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");
}
}
}