Nota:
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
El modelo de programación asincrónica de tareas (TAP) proporciona una capa de abstracción sobre la codificación asincrónica típica. En este modelo, escribirá código como una secuencia de instrucciones, igual que de costumbre. La diferencia es que puede leer el código basado en tareas a medida que el compilador procesa cada instrucción y antes de empezar a procesar la siguiente instrucción. Para lograr este modelo, el compilador realiza muchas transformaciones para completar cada tarea. Algunas instrucciones pueden iniciar el trabajo y devolver un Task objeto que representa el trabajo en curso y el compilador debe resolver estas transformaciones. El objetivo de la programación asincrónica de tareas es habilitar el código que lee como una secuencia de instrucciones, pero se ejecuta en un orden más complicado. La ejecución se basa en la asignación de recursos externos y cuando se completan las tareas.
El modelo de programación asincrónica de tareas es análogo a cómo las personas proporcionan instrucciones para los procesos que incluyen tareas asincrónicas. En este artículo se usa un ejemplo con instrucciones para preparar el desayuno para mostrar cómo las Async palabras clave y Await facilitan la razón del código que incluye una serie de instrucciones asincrónicas. Las instrucciones para hacer un desayuno se pueden proporcionar como una lista:
- Verte una taza de café.
- Calentar una sartén y luego freír dos huevos.
- Cocine tres tortitas de patata.
- Tosta dos rebanadas de pan.
- Unta mantequilla y mermelada en la tostada.
- Vierta un vaso de jugo de naranja.
Si tiene experiencia con la cocina, es posible que complete estas instrucciones de forma asincrónica. Empiece a calentar la sartén y luego empiece a cocinar la masa de patata. Pones el pan en la tostadora y luego empiezas a cocinar los huevos. En cada paso del proceso, inicia una tarea y, a continuación, realiza la transición a otras tareas que están listas para su atención.
El desayuno de cocina es un buen ejemplo de trabajo asincrónico que no es paralelo. Una persona (o hilo) puede manejar todas las tareas. Una persona puede hacer el desayuno de forma asincrónica iniciando la siguiente tarea antes de que se complete la tarea anterior. Cada tarea de cocción progresa independientemente de si alguien está viendo activamente el proceso. En cuanto empieces a calentar la sartén para los huevos, puedes empezar a cocinar los hash browns. Después de que las croquetas de patata comiencen a cocinarse, puede poner el pan en la tostadora.
Para un algoritmo paralelo, necesitas varias personas que cocinen (o varios hilos). Una persona cocina los huevos, otra cocina las croquetas de patata, y así sucesivamente. Cada persona se centra en su tarea específica. Cada persona que está cocinando (o cada subproceso) se bloquea sincrónicamente esperando a que se complete la tarea actual: Tortitas de patata listo para voltear, pan listo para aparecer en tostadora, etc.
Tenga en cuenta la misma lista de instrucciones sincrónicas escritas como instrucciones de código de Visual Basic:
Sub Main()
Dim cup As Coffee = PourCoffee()
Console.WriteLine("coffee is ready")
Dim eggs As Egg = FryEggs(2)
Console.WriteLine("eggs are ready")
Dim hashBrown As HashBrown = FryHashBrowns(3)
Console.WriteLine("hash browns are ready")
Dim toast As Toast = ToastBread(2)
ApplyButter(toast)
ApplyJam(toast)
Console.WriteLine("toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("oj is ready")
Console.WriteLine("Breakfast is ready!")
End Sub
Private Function PourOJ() As Juice
Console.WriteLine("Pouring orange juice")
Return New Juice()
End Function
Private Sub ApplyJam(toast As Toast)
Console.WriteLine("Putting jam on the toast")
End Sub
Private Sub ApplyButter(toast As Toast)
Console.WriteLine("Putting butter on the toast")
End Sub
Private Function ToastBread(slices As Integer) As Toast
For slice As Integer = 0 To slices - 1
Console.WriteLine("Putting a slice of bread in the toaster")
Next
Console.WriteLine("Start toasting...")
Task.Delay(3000).Wait()
Console.WriteLine("Remove toast from toaster")
Return New Toast()
End Function
Private Function FryHashBrowns(patties As Integer) As HashBrown
Console.WriteLine($"putting {patties} hash brown patties in the pan")
Console.WriteLine("cooking first side of hash browns...")
Task.Delay(3000).Wait()
For patty As Integer = 0 To patties - 1
Console.WriteLine("flipping a hash brown patty")
Next
Console.WriteLine("cooking the second side of hash browns...")
Task.Delay(3000).Wait()
Console.WriteLine("Put hash browns on plate")
Return New HashBrown()
End Function
Private Function FryEggs(howMany As Integer) As Egg
Console.WriteLine("Warming the egg pan...")
Task.Delay(3000).Wait()
Console.WriteLine($"cracking {howMany} eggs")
Console.WriteLine("cooking the eggs ...")
Task.Delay(3000).Wait()
Console.WriteLine("Put eggs on plate")
Return New Egg()
End Function
Private Function PourCoffee() As Coffee
Console.WriteLine("Pouring coffee")
Return New Coffee()
End Function
Si interpreta estas instrucciones como haría un ordenador, el desayuno tarda unos 30 minutos en prepararse. La duración es la suma de los tiempos de tarea individuales. El ordenador bloquea cada instrucción hasta que se completa todo el trabajo y, a continuación, continúa con la siguiente tarea. Este enfoque puede tardar mucho tiempo. En el ejemplo de desayuno, el método del ordenador crea un desayuno no satisfactorio. Las tareas posteriores de la lista sincrónica, como tostar el pan, no se inician hasta que se completen las tareas anteriores. Algunos alimentos se frían antes de que el desayuno esté listo para servir.
Si desea que el equipo ejecute instrucciones de forma asincrónica, debe escribir código asincrónico. Al escribir programas cliente, quiere que la interfaz de usuario responda a la entrada del usuario. La aplicación no debe inmovilizar toda la interacción al descargar datos de la web. Cuando escribes programas de servidor, no quieres bloquear hilos que podrían estar sirviendo otras peticiones. El uso de código sincrónico cuando existen alternativas asincrónicas daña la capacidad de escalar horizontalmente de forma menos costosa. Al final, los subprocesos bloqueados pasarán factura.
Las aplicaciones modernas correctas requieren código asincrónico. Sin soporte del lenguaje, la escritura de código asincrónico requiere callbacks, eventos de finalización u otros medios que pueden oscurecer la intención original del código. La ventaja del código sincrónico es la acción paso a paso que facilita el examen y la comprensión. Los modelos asincrónicos tradicionales le obligan a centrarse en la naturaleza asincrónica del código, no en las acciones fundamentales del código.
No bloquee, espere en su lugar
El código anterior resalta una práctica de programación desafortunada: Escritura de código sincrónico para realizar operaciones asincrónicas. El código impide que el subproceso actual realice cualquier otro trabajo. El código no interrumpe el subproceso mientras hay tareas en ejecución. El resultado de este modelo es similar a mirar en la tostadora después de poner el pan. Omite las interrupciones y no inicia otras tareas hasta que aparezca el pan. No sacas la mantequilla y la mermelada de la nevera. Podrías perderte ver un fuego comenzando en el fogón. Quieres tostar el pan y manejar otras tareas al mismo tiempo. Lo mismo sucede con tu código.
Puede empezar actualizando el código para que el subproceso no bloquee mientras se ejecutan las tareas. La palabra clave Await proporciona una manera de no bloqueo para iniciar una tarea y luego continuar la ejecución cuando se completa la tarea. Una versión asincrónica simple del código de desayuno tiene el siguiente aspecto:
Module AsyncBreakfastProgram
Async Function Main() As Task
Dim cup As Coffee = PourCoffee()
Console.WriteLine("coffee is ready")
Dim eggs As Egg = Await FryEggsAsync(2)
Console.WriteLine("eggs are ready")
Dim hashBrown As HashBrown = Await FryHashBrownsAsync(3)
Console.WriteLine("hash browns are ready")
Dim toast As Toast = Await ToastBreadAsync(2)
ApplyButter(toast)
ApplyJam(toast)
Console.WriteLine("toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("oj is ready")
Console.WriteLine("Breakfast is ready!")
End Function
Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
For slice As Integer = 0 To slices - 1
Console.WriteLine("Putting a slice of bread in the toaster")
Next
Console.WriteLine("Start toasting...")
Await Task.Delay(3000)
Console.WriteLine("Remove toast from toaster")
Return New Toast()
End Function
Private Async Function FryHashBrownsAsync(patties As Integer) As Task(Of HashBrown)
Console.WriteLine($"putting {patties} hash brown patties in the pan")
Console.WriteLine("cooking first side of hash browns...")
Await Task.Delay(3000)
For patty As Integer = 0 To patties - 1
Console.WriteLine("flipping a hash brown patty")
Next
Console.WriteLine("cooking the second side of hash browns...")
Await Task.Delay(3000)
Console.WriteLine("Put hash browns on plate")
Return New HashBrown()
End Function
Private Async Function FryEggsAsync(howMany As Integer) As Task(Of Egg)
Console.WriteLine("Warming the egg pan...")
Await Task.Delay(3000)
Console.WriteLine($"cracking {howMany} eggs")
Console.WriteLine("cooking the eggs ...")
Await Task.Delay(3000)
Console.WriteLine("Put eggs on plate")
Return New Egg()
End Function
Private Function PourCoffee() As Coffee
Console.WriteLine("Pouring coffee")
Return New Coffee()
End Function
Private Function PourOJ() As Juice
Console.WriteLine("Pouring orange juice")
Return New Juice()
End Function
Private Sub ApplyJam(toast As Toast)
Console.WriteLine("Putting jam on the toast")
End Sub
Private Sub ApplyButter(toast As Toast)
Console.WriteLine("Putting butter on the toast")
End Sub
End Module
El código actualiza los cuerpos de método originales de FryEggs, FryHashBrownsy ToastBread para devolver Task(Of Egg)objetos , Task(Of HashBrown)y Task(Of Toast) , respectivamente. Los nombres de método actualizados incluyen el sufijo "Async": FryEggsAsync, FryHashBrownsAsyncy ToastBreadAsync. La Main función devuelve el Task objeto, aunque no tiene una Return expresión, que es por diseño.
Nota:
El código actualizado aún no aprovecha las características clave de la programación asincrónica, lo que puede dar lugar a tiempos de finalización más cortos. El código procesa las tareas aproximadamente la misma cantidad de tiempo que la versión sincrónica inicial. Para ver las implementaciones completas del método, consulte la versión final del código más adelante en este artículo.
Vamos a aplicar el ejemplo de desayuno al código actualizado. El hilo no se bloquea mientras los huevos o las tortitas de patata se están cocinando, pero el código tampoco inicia otras tareas hasta que se completa el trabajo actual. Todavía pones el pan en la tostadora y mira a la tostadora hasta que el pan aparezca, pero ahora puedes responder a interrupciones. En un restaurante donde se realizan varios pedidos, el cocinero puede comenzar un nuevo pedido mientras que otro ya está cocinando.
En el código actualizado, el subproceso que trabaja en la tarea del desayuno no se bloquea mientras espera a que se termine cualquier tarea iniciada que esté sin finalizar. Para algunas aplicaciones, este cambio es todo lo que necesita. Puede permitir que la aplicación admita la interacción del usuario mientras se descargan datos desde la web. En otros escenarios, es posible que desee iniciar otras tareas mientras espera a que se complete la tarea anterior.
Iniciar tareas simultáneamente
Para la mayoría de las operaciones, desea iniciar varias tareas independientes inmediatamente. A medida que completes cada tarea, inicias otros trabajos que estén listos para empezar. Al aplicar esta metodología al ejemplo de desayuno, puede preparar el desayuno más rápidamente. También tiene todo listo cerca de la misma hora, por lo que podrá disfrutar de un desayuno caliente.
La Task clase y los tipos relacionados son clases que puede usar para aplicar este estilo de razonamiento a las tareas que están en curso. Este enfoque le permite escribir código que se parezca más a la forma en que se crea el desayuno en la vida real. Empiezas a cocinar los huevos, las patatas fritas ralladas y las tostadas al mismo tiempo. A medida que cada alimento requiere acción, diriges tu atención a esa tarea, realizas la acción y, a continuación, esperas a que haya algo más que requiera tu atención.
En tu código, inicias una tarea y retienes el objeto Task que representa el trabajo. Use el método Await en la tarea para retrasar la acción en el trabajo hasta que el resultado esté listo.
Aplique estos cambios al código de desayuno. El primer paso es almacenar las tareas de las operaciones cuando se inician, en lugar de usar la Await expresión :
Dim cup As Coffee = PourCoffee()
Console.WriteLine("Coffee is ready")
Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
Dim eggs As Egg = Await eggsTask
Console.WriteLine("Eggs are ready")
Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
Dim hashBrown As HashBrown = Await hashBrownTask
Console.WriteLine("Hash browns are ready")
Dim toastTask As Task(Of Toast) = ToastBreadAsync(2)
Dim toast As Toast = Await toastTask
ApplyButter(toast)
ApplyJam(toast)
Console.WriteLine("Toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("Oj is ready")
Console.WriteLine("Breakfast is ready!")
Estas revisiones no ayudan a preparar el desayuno más rápido. La Await expresión se aplica a todas las tareas tan pronto como se inician. El siguiente paso es mover las expresiones de Await para las tortitas de patata y los huevos al final del método, antes de servir el desayuno:
Dim cup As Coffee = PourCoffee()
Console.WriteLine("Coffee is ready")
Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
Dim toastTask As Task(Of Toast) = ToastBreadAsync(2)
Dim toast As Toast = Await toastTask
ApplyButter(toast)
ApplyJam(toast)
Console.WriteLine("Toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("Oj is ready")
Dim eggs As Egg = Await eggsTask
Console.WriteLine("Eggs are ready")
Dim hashBrown As HashBrown = Await hashBrownTask
Console.WriteLine("Hash browns are ready")
Console.WriteLine("Breakfast is ready!")
Ahora tiene un desayuno preparado de forma asincrónica que tarda unos 20 minutos en prepararse. El tiempo total de cocción se reduce porque algunas tareas se ejecutan simultáneamente.
El código actualiza el proceso de preparación reduciendo el tiempo de cocción, pero introduce una regresión al quemar los huevos y los hash browns. Inicia todas las tareas asincrónicas a la vez. Y esperará por una tarea solo cuando necesite los resultados. El código puede ser similar al programa de una aplicación web que realiza solicitudes a diferentes microservicios y, a continuación, combina los resultados en una sola página. Realiza todas las solicitudes inmediatamente y, a continuación, aplica la Await expresión en todas esas tareas y redacta la página web.
Soporte para la composición con tareas
Las revisiones de código anteriores ayudan a preparar todo para el desayuno al mismo tiempo, excepto la tostada. El proceso de hacer la tostada es una composición de una operación asincrónica (tostar el pan) con operaciones sincrónicas (untar mantequilla y mermelada en la tostada). En este ejemplo se muestra un concepto importante sobre la programación asincrónica:
Importante
La composición de una operación asincrónica seguida del trabajo sincrónico es una operación asincrónica. Se indicó otra manera, si alguna parte de una operación es asincrónica, toda la operación es asincrónica.
En las actualizaciones anteriores, aprendiste a utilizar objetos Task o Task<TResult> para retener tareas en ejecución. Espera en cada tarea antes de usar su resultado. El siguiente paso es crear métodos que representen la combinación de otro trabajo. Antes de servir el desayuno, querrá esperar en la tarea que representa tostar el pan antes de distribuir la mantequilla y la mermelada.
Puede representar este trabajo con el código siguiente:
Async Function MakeToastWithButterAndJamAsync(number As Integer) As Task(Of Toast)
Dim toast As Toast = Await ToastBreadAsync(number)
ApplyButter(toast)
ApplyJam(toast)
Return toast
End Function
El método MakeToastWithButterAndJamAsync tiene el modificador Async en su firma que señala al compilador que el método contiene una expresión Await y contiene operaciones asíncronas. Este método representa la tarea que tuesta el pan y, después, extiende la mantequilla y la mermelada. El método devuelve un Task<TResult> objeto que representa la composición de las tres operaciones.
El bloque principal revisado de código ahora tiene el siguiente aspecto:
Async Function Main() As Task
Dim cup As Coffee = PourCoffee()
Console.WriteLine("coffee is ready")
Dim eggsTask = FryEggsAsync(2)
Dim hashBrownTask = FryHashBrownsAsync(3)
Dim toastTask = MakeToastWithButterAndJamAsync(2)
Dim eggs = Await eggsTask
Console.WriteLine("eggs are ready")
Dim hashBrown = Await hashBrownTask
Console.WriteLine("hash browns are ready")
Dim toast = Await toastTask
Console.WriteLine("toast is ready")
Dim oj As Juice = PourOJ()
Console.WriteLine("oj is ready")
Console.WriteLine("Breakfast is ready!")
End Function
Este cambio de código ilustra una técnica importante para trabajar con código asincrónico. Puedes componer tareas separando las operaciones en un método nuevo que devuelva una tarea. Puede elegir cuándo esperar para continuar con esa tarea. Puede iniciar otras tareas simultáneamente.
Control de excepciones asincrónicas
Hasta este punto, el código asume implícitamente que todas las tareas se completan correctamente. Los métodos asincrónicos lanzan excepciones, al igual que sus homólogos sincrónicos. Los objetivos de compatibilidad asincrónica con excepciones y control de errores son los mismos que para la compatibilidad asincrónica en general. El procedimiento recomendado es escribir código que lea como una serie de instrucciones sincrónicas. Las tareas lanzan excepciones cuando no se pueden completar exitosamente. El código de cliente puede detectar esas excepciones cuando la Await expresión se aplica a una tarea iniciada.
En el ejemplo del desayuno, supongamos que la tostadora se activa mientras tosta el pan. Puede simular ese problema modificando el ToastBreadAsync método para que coincida con el código siguiente:
Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
For slice As Integer = 0 To slices - 1
Console.WriteLine("Putting a slice of bread in the toaster")
Next
Console.WriteLine("Start toasting...")
Await Task.Delay(2000)
Console.WriteLine("Fire! Toast is ruined!")
Throw New InvalidOperationException("The toaster is on fire")
Await Task.Delay(1000)
Console.WriteLine("Remove toast from toaster")
Return New Toast()
End Function
Nota:
Al compilar este código, verá una advertencia sobre el código inaccesible. Se trata de un error por diseño. Después de que el tostador se active, las operaciones no continúan normalmente y el código devuelve un error.
Después de realizar los cambios en el código, ejecute la aplicación y compruebe la salida:
Pouring coffee
Coffee is ready
Warming the egg pan...
putting 3 hash brown patties in the pan
Cooking first side of hash browns...
Putting a slice of bread in the toaster
Putting a slice of bread in the toaster
Start toasting...
Fire! Toast is ruined!
Flipping a hash brown patty
Flipping a hash brown patty
Flipping a hash brown patty
Cooking the second side of hash browns...
Cracking 2 eggs
Cooking the eggs ...
Put hash browns on plate
Put eggs on plate
Eggs are ready
Hash browns are ready
Unhandled exception. System.InvalidOperationException: The toaster is on fire
at AsyncBreakfast.Program.ToastBreadAsync(Int32 slices) in Program.vb:line 65
at AsyncBreakfast.Program.MakeToastWithButterAndJamAsync(Int32 number) in Program.vb:line 36
at AsyncBreakfast.Program.Main(String[] args) in Program.vb:line 24
at AsyncBreakfast.Program.<Main>(String[] args)
Tenga en cuenta que bastantes tareas finalizan entre el momento en que el tostador se incendia y el sistema observa la excepción. Cuando una tarea que se ejecuta de forma asincrónica produce una excepción, se produce un error en esa tarea. El objeto Task almacena la excepción que se lanzó en la propiedad Task.Exception. Las tareas con errores lanzan la excepción cuando se aplica la Await expresión a la tarea.
Hay dos mecanismos importantes para comprender este proceso:
- Cómo se almacena una excepción en una tarea con errores.
- Cómo se desempaqueta una excepción y se vuelve a lanzar cuando el código espera (
Await) en una tarea con errores.
Cuando el código que se ejecuta de forma asincrónica produce una excepción, la excepción se almacena en el Task objeto . La Task.Exception propiedad es un AggregateException objeto porque se puede producir más de una excepción durante el trabajo asincrónico. Cualquier excepción lanzada se agrega a la colección AggregateException.InnerExceptions. Si la Exception propiedad es null, se crea un nuevo AggregateException objeto y la excepción iniciada es el primer elemento de la colección.
El escenario más común para una tarea con errores es que la Exception propiedad contiene exactamente una excepción. Cuando su código espera en una tarea con error, vuelva a lanzar la primera excepción AggregateException.InnerExceptions de la colección. Este resultado es la razón por la que la salida del ejemplo muestra un InvalidOperationException objeto en lugar de un AggregateException objeto. La extracción de la primera excepción interna hace que el trabajo con métodos asincrónicos sea lo más parecido posible a trabajar con sus homólogos sincrónicos. Puede examinar la propiedad Exception en su código cuando su escenario podría generar varias excepciones.
Sugerencia
La práctica recomendada es que las excepciones de validación de argumentos surjan sincrónicamente de los métodos que devuelven tareas. Para obtener más información y ejemplos, vea Excepciones en métodos que devuelven tareas.
Antes de continuar con la sección siguiente, comente las dos instrucciones siguientes en el método ToastBreadAsync. No quieres iniciar otro incendio:
' Console.WriteLine("Fire! Toast is ruined!")
' Throw New InvalidOperationException("The toaster is on fire")
Aplicar expresiones await a tareas de forma eficaz
Puede mejorar la serie de Await expresiones al final del código anterior mediante métodos de la Task clase . Una API es el WhenAll método , que devuelve un Task objeto que se completa cuando se completan todas las tareas de su lista de argumentos. El código siguiente muestra este método:
Await Task.WhenAll(eggsTask, hashBrownTask, toastTask)
Console.WriteLine("Eggs are ready")
Console.WriteLine("Hash browns are ready")
Console.WriteLine("Toast is ready")
Console.WriteLine("Breakfast is ready!")
Otra opción es usar el WhenAny método , que devuelve un Task(Of Task) objeto que se completa cuando se completa cualquiera de sus argumentos. Puede esperar la tarea devuelta porque sabe que la tarea está terminada. En el código siguiente se muestra cómo puede usar el WhenAny método para esperar a que finalice la primera tarea y, a continuación, procesar su resultado. Después de procesar el resultado de la tarea completada, quite la tarea completada de la lista de tareas pasadas al WhenAny método .
Module ConcurrentBreakfastProgram
Async Function Main() As Task
Dim cup As Coffee = PourCoffee()
Console.WriteLine("Coffee is ready")
Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
Dim toastTask As Task(Of Toast) = MakeToastWithButterAndJamAsync(2)
Dim breakfastTasks As New List(Of Task) From {eggsTask, hashBrownTask, toastTask}
While breakfastTasks.Count > 0
Dim finishedTask As Task = Await Task.WhenAny(breakfastTasks)
If finishedTask Is eggsTask Then
Console.WriteLine("eggs are ready")
ElseIf finishedTask Is hashBrownTask Then
Console.WriteLine("hash browns are ready")
ElseIf finishedTask Is toastTask Then
Console.WriteLine("toast is ready")
End If
Await finishedTask
breakfastTasks.Remove(finishedTask)
End While
Dim oj As Juice = PourOJ()
Console.WriteLine("oj is ready")
Console.WriteLine("Breakfast is ready!")
End Function
Async Function MakeToastWithButterAndJamAsync(number As Integer) As Task(Of Toast)
Dim toast As Toast = Await ToastBreadAsync(number)
ApplyButter(toast)
ApplyJam(toast)
Return toast
End Function
Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
For slice As Integer = 0 To slices - 1
Console.WriteLine("Putting a slice of bread in the toaster")
Next
Console.WriteLine("Start toasting...")
Await Task.Delay(3000)
Console.WriteLine("Remove toast from toaster")
Return New Toast()
End Function
Private Async Function FryHashBrownsAsync(patties As Integer) As Task(Of HashBrown)
Console.WriteLine($"putting {patties} hash brown patties in the pan")
Console.WriteLine("cooking first side of hash browns...")
Await Task.Delay(3000)
For patty As Integer = 0 To patties - 1
Console.WriteLine("flipping a hash brown patty")
Next
Console.WriteLine("cooking the second side of hash browns...")
Await Task.Delay(3000)
Console.WriteLine("Put hash browns on plate")
Return New HashBrown()
End Function
Private Async Function FryEggsAsync(howMany As Integer) As Task(Of Egg)
Console.WriteLine("Warming the egg pan...")
Await Task.Delay(3000)
Console.WriteLine($"cracking {howMany} eggs")
Console.WriteLine("cooking the eggs ...")
Await Task.Delay(3000)
Console.WriteLine("Put eggs on plate")
Return New Egg()
End Function
Private Function PourCoffee() As Coffee
Console.WriteLine("Pouring coffee")
Return New Coffee()
End Function
Private Function PourOJ() As Juice
Console.WriteLine("Pouring orange juice")
Return New Juice()
End Function
Private Sub ApplyJam(toast As Toast)
Console.WriteLine("Putting jam on the toast")
End Sub
Private Sub ApplyButter(toast As Toast)
Console.WriteLine("Putting butter on the toast")
End Sub
End Module
Cerca del final del fragmento de código, observe la Await finishedTask expresión. Esta línea es importante porque Task.WhenAny devuelve una Task(Of Task) tarea contenedora que contiene la tarea completada. Cuando , Await Task.WhenAnyestá esperando a que se complete la tarea contenedora y el resultado es la tarea real que finalizó primero. Sin embargo, para recuperar el resultado de esa tarea o asegurarse de que se produzcan correctamente las excepciones, debe realizar Await la propia tarea completada (almacenada en finishedTask). Aunque sepa que la tarea ha finalizado, a la espera de nuevo le permite acceder a su resultado o controlar las excepciones que podrían haber provocado un error.
Revisión del código final
Este es el aspecto de la versión final del código:
Module ConcurrentBreakfastProgram
Async Function Main() As Task
Dim cup As Coffee = PourCoffee()
Console.WriteLine("Coffee is ready")
Dim eggsTask As Task(Of Egg) = FryEggsAsync(2)
Dim hashBrownTask As Task(Of HashBrown) = FryHashBrownsAsync(3)
Dim toastTask As Task(Of Toast) = MakeToastWithButterAndJamAsync(2)
Dim breakfastTasks As New List(Of Task) From {eggsTask, hashBrownTask, toastTask}
While breakfastTasks.Count > 0
Dim finishedTask As Task = Await Task.WhenAny(breakfastTasks)
If finishedTask Is eggsTask Then
Console.WriteLine("eggs are ready")
ElseIf finishedTask Is hashBrownTask Then
Console.WriteLine("hash browns are ready")
ElseIf finishedTask Is toastTask Then
Console.WriteLine("toast is ready")
End If
Await finishedTask
breakfastTasks.Remove(finishedTask)
End While
Dim oj As Juice = PourOJ()
Console.WriteLine("oj is ready")
Console.WriteLine("Breakfast is ready!")
End Function
Async Function MakeToastWithButterAndJamAsync(number As Integer) As Task(Of Toast)
Dim toast As Toast = Await ToastBreadAsync(number)
ApplyButter(toast)
ApplyJam(toast)
Return toast
End Function
Private Async Function ToastBreadAsync(slices As Integer) As Task(Of Toast)
For slice As Integer = 0 To slices - 1
Console.WriteLine("Putting a slice of bread in the toaster")
Next
Console.WriteLine("Start toasting...")
Await Task.Delay(3000)
Console.WriteLine("Remove toast from toaster")
Return New Toast()
End Function
Private Async Function FryHashBrownsAsync(patties As Integer) As Task(Of HashBrown)
Console.WriteLine($"putting {patties} hash brown patties in the pan")
Console.WriteLine("cooking first side of hash browns...")
Await Task.Delay(3000)
For patty As Integer = 0 To patties - 1
Console.WriteLine("flipping a hash brown patty")
Next
Console.WriteLine("cooking the second side of hash browns...")
Await Task.Delay(3000)
Console.WriteLine("Put hash browns on plate")
Return New HashBrown()
End Function
Private Async Function FryEggsAsync(howMany As Integer) As Task(Of Egg)
Console.WriteLine("Warming the egg pan...")
Await Task.Delay(3000)
Console.WriteLine($"cracking {howMany} eggs")
Console.WriteLine("cooking the eggs ...")
Await Task.Delay(3000)
Console.WriteLine("Put eggs on plate")
Return New Egg()
End Function
Private Function PourCoffee() As Coffee
Console.WriteLine("Pouring coffee")
Return New Coffee()
End Function
Private Function PourOJ() As Juice
Console.WriteLine("Pouring orange juice")
Return New Juice()
End Function
Private Sub ApplyJam(toast As Toast)
Console.WriteLine("Putting jam on the toast")
End Sub
Private Sub ApplyButter(toast As Toast)
Console.WriteLine("Putting butter on the toast")
End Sub
End Module
El código completa las tareas de desayuno asincrónicas en unos 15 minutos. El tiempo total se reduce porque algunas tareas se ejecutan simultáneamente. El código supervisa simultáneamente varias tareas y realiza acciones solo según sea necesario.
El código final es asincrónico. Refleja con más precisión cómo una persona puede cocinar el desayuno. Compare el código final con el primer ejemplo de código del artículo. Las acciones principales siguen siendo claras leyendo el código. Puede leer el código final de la misma manera que leyó la lista de instrucciones para hacer un desayuno, como se muestra al principio del artículo. Las características del lenguaje para las palabras clave Async y Await proporcionan la traducción que cada persona realiza para seguir las instrucciones escritas: Inicia las tareas tan pronto como puedas y no te bloquees mientras esperas a que se completen.
Async/await frente a ContinueWith
Las palabras clave Async y Await simplifican la sintaxis en comparación con el uso directo de ContinueWith. Aunque Async/Await y ContinueWith tienen una semántica similar para controlar las operaciones asincrónicas, el compilador no traduce necesariamente Await directamente en llamadas al método ContinueWith. En su lugar, el compilador genera código de máquina de estado optimizado que proporciona el mismo comportamiento lógico. Esta transformación proporciona ventajas significativas de legibilidad y mantenimiento, especialmente al encadenar varias operaciones asincrónicas.
Considere un escenario en el que debe realizar varias operaciones asincrónicas secuenciales. Este es el aspecto de la misma lógica cuando se implementa con ContinueWith en comparación con Async/Await.
Uso de ContinueWith
Con ContinueWith, cada paso de una secuencia de operaciones asincrónicas requiere continuaciones anidadas:
' Using ContinueWith - demonstrates the complexity when chaining operations
Function MakeBreakfastWithContinueWith() As Task
Return StartCookingEggsAsync() _
.ContinueWith(Function(eggsTask)
Dim eggs = eggsTask.Result
Console.WriteLine("Eggs ready, starting bacon...")
Return StartCookingBaconAsync()
End Function) _
.Unwrap() _
.ContinueWith(Function(baconTask)
Dim bacon = baconTask.Result
Console.WriteLine("Bacon ready, starting toast...")
Return StartToastingBreadAsync()
End Function) _
.Unwrap() _
.ContinueWith(Function(toastTask)
Dim toast = toastTask.Result
Console.WriteLine("Toast ready, applying butter...")
Return ApplyButterAsync(toast)
End Function) _
.Unwrap() _
.ContinueWith(Function(butteredToastTask)
Dim butteredToast = butteredToastTask.Result
Console.WriteLine("Butter applied, applying jam...")
Return ApplyJamAsync(butteredToast)
End Function) _
.Unwrap() _
.ContinueWith(Sub(finalToastTask)
Dim finalToast = finalToastTask.Result
Console.WriteLine("Breakfast completed with ContinueWith!")
End Sub)
End Function
Uso de Async/Await
La misma secuencia de operaciones con Async/Await resulta mucho más natural.
' Using Async/Await - much cleaner and easier to read
Async Function MakeBreakfastWithAsyncAwait() As Task
Dim eggs = Await StartCookingEggsAsync()
Console.WriteLine("Eggs ready, starting bacon...")
Dim bacon = Await StartCookingBaconAsync()
Console.WriteLine("Bacon ready, starting toast...")
Dim toast = Await StartToastingBreadAsync()
Console.WriteLine("Toast ready, applying butter...")
Dim butteredToast = Await ApplyButterAsync(toast)
Console.WriteLine("Butter applied, applying jam...")
Dim finalToast = Await ApplyJamAsync(butteredToast)
Console.WriteLine("Breakfast completed with Async/Await!")
End Function
¿Por qué se prefiere Async/Await?
El Async/Await enfoque ofrece varias ventajas:
- Legibilidad: el código lee como código sincrónico, lo que facilita la comprensión del flujo de operaciones.
- Capacidad de mantenimiento: agregar o quitar pasos en la secuencia requiere cambios mínimos en el código.
-
Control de errores: el control de excepciones con
Try/Catchbloques funciona de forma natural, mientras queContinueWithrequiere un control cuidadoso de las tareas con errores. -
Depuración: La experiencia con la pila de llamadas y el depurador es mucho mejor con
Async/Await. -
Rendimiento: las optimizaciones del compilador para
Async/Awaitson más sofisticadas que las cadenas manuales.ContinueWith
La ventaja se vuelve aún más evidente a medida que aumenta el número de operaciones encadenadas. Aunque una sola continuación puede ser manejable con ContinueWith, las secuencias de 3 a 4 o más operaciones asincrónicas se vuelven rápidamente difíciles de leer y mantener. Este patrón, conocido como "notación monádica" en la programación funcional, permite componer varias operaciones asincrónicas de una manera secuencial y legible.
Consulte también
- Await (operador)
- Async
- Tutorial: Acceso a la Web mediante Async y Await (Visual Basic)
- Async Return Types (Visual Basic) (Tipos de valor devuelto de Async [Visual Basic])
- Patrón asincrónico basado en tareas (TAP)