Controlar cómo se vuelve a entrar en aplicaciones asincrónicas (C# y Visual Basic)

Cuando se incluye código asincrónico en la aplicación, se debe considerar y evitar posiblemente volver a entrar, lo que hace referencia a repetir una operación asincrónica antes de haberse completado. Si no identifica y ni controla las posibilidades para volver a entrar, puede producir resultados inesperados.

En este tema

Nota

Las instrucciones incluidas en Revisar y ejecutar la aplicación de ejemplo muestran cómo ejecutar el código tanto como aplicación de Windows Presentation Foundation (WPF) como aplicación de la Tienda Windows.

Para ejecutar el ejemplo como una aplicación WPF, debe tener Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 para escritorio de Windows, Visual Studio Express 2013 para Windows, o .NET Framework 4.5 o 4.5.1 instalado en su equipo.

Para ejecutar el ejemplo como una aplicación Tienda Windows, debe tener Windows 8 instalado en el equipo.Además, si desea ejecutar el ejemplo desde Visual Studio, también debe tener instalados Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 para escritorio de Windows, o Visual Studio Express 2013 para Windows.

Reconocer la reentrada

En el ejemplo de este tema, los usuarios eligen un botón Inicio para iniciar una aplicación asincrónica que descarga una serie de sitios web y calcula el número total de bytes descargados. Una versión sincrónica del ejemplo respondería la misma manera independientemente de cuántas veces elija un usuario el botón porque, después de la primera vez, el subproceso de la interfaz de usuario omite esos eventos hasta que la aplicación termina de ejecutarse. En una aplicación asincrónica, sin embargo, el subproceso de interfaz de usuario sigue respondiendo y puede que se repita la operación asincrónica antes de haberse completado.

En el ejemplo siguiente se muestra el resultado esperado si el usuario elige el botón Inicio solo una vez. Una lista de sitios web descargados aparece con el tamaño, en bytes, de cada sitio. El número total de bytes aparece al final.

1. msdn.microsoft.com/library/hh191443.aspx                83732
2. msdn.microsoft.com/library/aa578028.aspx               205273
3. msdn.microsoft.com/library/jj155761.aspx                29019
4. msdn.microsoft.com/library/hh290140.aspx               117152
5. msdn.microsoft.com/library/hh524395.aspx                68959
6. msdn.microsoft.com/library/ms404677.aspx               197325
7. msdn.microsoft.com                                            42972
8. msdn.microsoft.com/library/ff730837.aspx               146159

TOTAL bytes returned:  890591

Sin embargo, si el usuario elige el botón más de una vez, se invoca el controlador de eventos repetidamente, y el proceso de descarga se repite cada vez. Como resultado, varias operaciones asincrónicas se ejecutan simultáneamente, la salida interpola los resultados y el número total de bytes es confuso.

1. msdn.microsoft.com/library/hh191443.aspx                83732
2. msdn.microsoft.com/library/aa578028.aspx               205273
3. msdn.microsoft.com/library/jj155761.aspx                29019
4. msdn.microsoft.com/library/hh290140.aspx               117152
5. msdn.microsoft.com/library/hh524395.aspx                68959
1. msdn.microsoft.com/library/hh191443.aspx                83732
2. msdn.microsoft.com/library/aa578028.aspx               205273
6. msdn.microsoft.com/library/ms404677.aspx               197325
3. msdn.microsoft.com/library/jj155761.aspx                29019
7. msdn.microsoft.com                                            42972
4. msdn.microsoft.com/library/hh290140.aspx               117152
8. msdn.microsoft.com/library/ff730837.aspx               146159

TOTAL bytes returned:  890591

5. msdn.microsoft.com/library/hh524395.aspx                68959
1. msdn.microsoft.com/library/hh191443.aspx                83732
2. msdn.microsoft.com/library/aa578028.aspx               205273
6. msdn.microsoft.com/library/ms404677.aspx               197325
3. msdn.microsoft.com/library/jj155761.aspx                29019
4. msdn.microsoft.com/library/hh290140.aspx               117152
7. msdn.microsoft.com                                            42972
5. msdn.microsoft.com/library/hh524395.aspx                68959
8. msdn.microsoft.com/library/ff730837.aspx               146159

TOTAL bytes returned:  890591

6. msdn.microsoft.com/library/ms404677.aspx               197325
7. msdn.microsoft.com                                            42972
8. msdn.microsoft.com/library/ff730837.aspx               146159

TOTAL bytes returned:  890591

Puede revisar el código que genera este resultado si se desplaza al final de este tema. Puede experimentar con el código si descarga la solución en el equipo local y después ejecuta el proyecto WebsiteDownload o si usa el código al final de este tema para crear su propio proyecto. Para obtener más información e instrucciones, vea Revisar y ejecutar la aplicación de ejemplo.

Controlar la reentrada

Puede administrar cómo volver a entrar de diversas maneras, según lo que desee que haga la aplicación. Este tema muestra los ejemplos siguientes:

  • Deshabilitar el botón Iniciar

    Deshabilite el botón Iniciar mientras la operación se está ejecutando para que el usuario no pueda interrumpirla.

  • Cancelar y reiniciar la operación

    Cancele cualquier operación que todavía se esté ejecutando cuando el usuario elija el botón Inicio de nuevo y, a continuación, deje que continúe la última operación solicitada.

  • Ejecutar varias operaciones y poner en cola el resultado

    Permita que todas las operaciones solicitadas se ejecuten de forma asincrónica, pero coordine la presentación de la salida de modo que los resultados de cada operación aparezcan juntos y en orden.

Deshabilitar el botón Iniciar

Puede bloquear el botón Menú de Inicio mientras una operación se está ejecutando si deshabilita el botón situado en la parte superior del controlador de eventos StartButton_Click. A continuación puede volver a habilitar el botón dentro de un bloque finally cuando finalice la operación de modo que los usuarios puedan ejecutar la aplicación de nuevo.

El código siguiente muestra estos cambios, marcados con asteriscos. Puede agregar los cambios en el código al final de este tema o puede descargar la aplicación finalizada en Ejemplos de Async: volver a entrar en aplicaciones de escritorio de .NET ella Ejemplos de Async: volver a entrar en Aplicaciones de la Tienda Windows. El nombre del proyecto es DisableStartButton.

Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)
    ' This line is commented out to make the results clearer in the output.
    'ResultsTextBox.Text = ""

    ' ***Disable the Start button until the downloads are complete. 
    StartButton.IsEnabled = False

    Try
        Await AccessTheWebAsync()

    Catch ex As Exception
        ResultsTextBox.Text &= vbCrLf & "Downloads failed."
    ' ***Enable the Start button in case you want to run the program again. 
    Finally
        StartButton.IsEnabled = True

    End Try
End Sub
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
    // This line is commented out to make the results clearer in the output.
    //ResultsTextBox.Text = "";

    // ***Disable the Start button until the downloads are complete. 
    StartButton.IsEnabled = false; 

    try
    {
        await AccessTheWebAsync();
    }
    catch (Exception)
    {
        ResultsTextBox.Text += "\r\nDownloads failed.";
    }
    // ***Enable the Start button in case you want to run the program again. 
    finally
    {
        StartButton.IsEnabled = true;
    }
}

Como resultado de los cambios, el botón no responde mientras se está descargando AccessTheWebAsync, así que no se puede volver a entrar en el proceso.

Cancelar y reiniciar la operación

En lugar de deshabilitar el botón Inicio, puede mantenerlo activo, pero, si el usuario elige ese botón otra vez, cancele la operación que ya se está ejecutando y permita que la operación iniciada más recientemente continúe.

Para obtener más información sobre la cancelación, vea Ajuste de la aplicación asincrónica.

Para configurar este escenario, realice los cambios siguientes en el código básico que se proporciona en Revisar y ejecutar la aplicación de ejemplo. También puede descargar la aplicación finalizada de Ejemplos de Async: volver a entrar en aplicaciones de escritorio de .NET o Ejemplos de Async: volver a entrar en Aplicaciones de la Tienda Windows. El nombre de este proyecto es CancelAndRestart.

  1. Declare una variable CancellationTokenSource, cts, que está en el ámbito de todos los métodos.

    Class MainWindow // Or Class MainPage
    
        ' *** Declare a System.Threading.CancellationTokenSource.
        Dim cts As CancellationTokenSource
    
    public partial class MainWindow : Window   // Or class MainPage
    {
        // *** Declare a System.Threading.CancellationTokenSource.
        CancellationTokenSource cts;
    
  2. En StartButton_Click, determine si una operación ya se está produciendo. Si el valor de cts es null (Nothing en Visual Basic), ya no hay ninguna operación activa. Si el valor no es null, la operación que se está ejecutando se cancela.

    ' *** If a download process is already underway, cancel it.
    If cts IsNot Nothing Then
        cts.Cancel()
    End If
    
    // *** If a download process is already underway, cancel it.
    if (cts != null)
    {
        cts.Cancel();
    }
    
  3. Establezca cts en un valor diferente que representa el proceso actual.

    ' *** Now set cts to cancel the current process if the button is chosen again.
    Dim newCTS As CancellationTokenSource = New CancellationTokenSource()
    cts = newCTS
    
    // *** Now set cts to a new value that you can use to cancel the current process
    // if the button is chosen again.
    CancellationTokenSource newCTS = new CancellationTokenSource();
    cts = newCTS;
    
  4. Al final de StartButton_Click se completa el proceso actual, así que debe establecer el valor cts en null.

    ' *** When the process completes, signal that another process can proceed.
    If cts Is newCTS Then
        cts = Nothing
    End If
    
    // *** When the process is complete, signal that another process can begin.
    if (cts == newCTS)
        cts = null;
    

El código siguiente muestra todos los cambios en StartButton_Click. Las adiciones se marcan con asteriscos.

Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)

    ' This line is commented out to make the results clearer. 
    'ResultsTextBox.Text = ""


    ' *** If a download process is underway, cancel it.
    If cts IsNot Nothing Then
        cts.Cancel()
    End If

    ' *** Now set cts to cancel the current process if the button is chosen again.
    Dim newCTS As CancellationTokenSource = New CancellationTokenSource()
    cts = newCTS

    Try
        ' *** Send a token to carry the message if the operation is canceled.
        Await AccessTheWebAsync(cts.Token)
        

    Catch ex As OperationCanceledException
        ResultsTextBox.Text &= vbCrLf & "Download canceled." & vbCrLf

    Catch ex As Exception
        ResultsTextBox.Text &= vbCrLf & "Downloads failed."
    End Try

    ' *** When the process is complete, signal that another process can proceed.
    If cts Is newCTS Then
        cts = Nothing
    End If
End Sub
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
    // This line is commented out to make the results clearer in the output.
    //ResultsTextBox.Clear();

    // *** If a download process is already underway, cancel it.
    if (cts != null)
    {
        cts.Cancel();
    }

    // *** Now set cts to cancel the current process if the button is chosen again.
    CancellationTokenSource newCTS = new CancellationTokenSource();
    cts = newCTS;

    try
    {
        // ***Send cts.Token to carry the message if there is a cancellation request.
        await AccessTheWebAsync(cts.Token);
        
    }
    // *** Catch cancellations separately.
    catch (OperationCanceledException)
    {
        ResultsTextBox.Text += "\r\nDownloads canceled.\r\n";
    }
    catch (Exception)
    {
        ResultsTextBox.Text += "\r\nDownloads failed.\r\n";
    }
    // *** When the process is complete, signal that another process can proceed.
    if (cts == newCTS)
        cts = null;
}

En AccessTheWebAsync, realice los cambios siguientes.

  • Agregue un parámetro para aceptar el token de cancelación de StartButton_Click.

  • Utilice el método GetAsync para descargar los sitios web porque GetAsync acepta un argumento CancellationToken.

  • Antes de llamar a DisplayResults para mostrar los resultados para cada sitio web descargado, compruebe ct para comprobar que la operación actual no se haya cancelado.

El código siguiente muestra estos cambios, marcados con asteriscos.

' *** Provide a parameter for the CancellationToken from StartButton_Click.
Private Async Function AccessTheWebAsync(ct As CancellationToken) As Task

    ' Declare an HttpClient object.
    Dim client = New HttpClient()

    ' Make a list of web addresses.
    Dim urlList As List(Of String) = SetUpURLList()

    Dim total = 0
    Dim position = 0

    For Each url In urlList
        ' *** Use the HttpClient.GetAsync method because it accepts a 
        ' cancellation token.
        Dim response As HttpResponseMessage = Await client.GetAsync(url, ct)

        ' *** Retrieve the website contents from the HttpResponseMessage.
        Dim urlContents As Byte() = Await response.Content.ReadAsByteArrayAsync()

        ' *** Check for cancellations before displaying information about the 
        ' latest site. 
        ct.ThrowIfCancellationRequested()

        position += 1
        DisplayResults(url, urlContents, position)

        ' Update the total.
        total += urlContents.Length
    Next

    ' Display the total count for all of the websites.
    ResultsTextBox.Text &=
        String.Format(vbCrLf & vbCrLf & "TOTAL bytes returned:  " & total & vbCrLf)
End Function
// *** Provide a parameter for the CancellationToken from StartButton_Click.
async Task AccessTheWebAsync(CancellationToken ct)
{
    // Declare an HttpClient object.
    HttpClient client = new HttpClient();

    // Make a list of web addresses.
    List<string> urlList = SetUpURLList();

    var total = 0;
    var position = 0;

    foreach (var url in urlList)
    {
        // *** Use the HttpClient.GetAsync method because it accepts a 
        // cancellation token.
        HttpResponseMessage response = await client.GetAsync(url, ct);

        // *** Retrieve the website contents from the HttpResponseMessage.
        byte[] urlContents = await response.Content.ReadAsByteArrayAsync();

        // *** Check for cancellations before displaying information about the 
        // latest site. 
        ct.ThrowIfCancellationRequested();

        DisplayResults(url, urlContents, ++position);

        // Update the total.
        total += urlContents.Length;
    }

    // Display the total count for all of the websites.
    ResultsTextBox.Text +=
        string.Format("\r\n\r\nTOTAL bytes returned:  {0}\r\n", total);
}   

Si elige el botón Inicio varias veces mientras esta aplicación se está ejecutando, se generarán resultados parecidos al siguiente resultado.

1. msdn.microsoft.com/library/hh191443.aspx                83732
2. msdn.microsoft.com/library/aa578028.aspx               205273
3. msdn.microsoft.com/library/jj155761.aspx                29019
4. msdn.microsoft.com/library/hh290140.aspx               122505
5. msdn.microsoft.com/library/hh524395.aspx                68959
6. msdn.microsoft.com/library/ms404677.aspx               197325
Download canceled.

1. msdn.microsoft.com/library/hh191443.aspx                83732
2. msdn.microsoft.com/library/aa578028.aspx               205273
3. msdn.microsoft.com/library/jj155761.aspx                29019
Download canceled.

1. msdn.microsoft.com/library/hh191443.aspx                83732
2. msdn.microsoft.com/library/aa578028.aspx               205273
3. msdn.microsoft.com/library/jj155761.aspx                29019
4. msdn.microsoft.com/library/hh290140.aspx               117152
5. msdn.microsoft.com/library/hh524395.aspx                68959
6. msdn.microsoft.com/library/ms404677.aspx               197325
7. msdn.microsoft.com                                            42972
8. msdn.microsoft.com/library/ff730837.aspx               146159

TOTAL bytes returned:  890591

Para eliminar listas parciales, quite el comentario de la primera línea de código en StartButton_Click para borrar el cuadro de texto cada vez que el usuario reinicia la operación.

Ejecutar varias operaciones y poner en cola el resultado

Este tercer ejemplo es el más complejo porque la aplicación inicia otra operación asincrónica cada vez que el usuario elige el botón Inicio y todas las operaciones se ejecutan hasta su finalización. Todas las operaciones solicitadas descargan sitios web de la lista de forma asincrónica, pero muestran el resultado de las operaciones secuencialmente. Es decir, la actividad de descarga real se intercala, como se muestra en el resultado de Reconocer la reentrada, pero la lista de resultados para cada grupo se muestra por separado.

Las operaciones comparten una Taskglobal, pendingWork, que actúa como equipo selector para el proceso de presentación.

Puede ejecutar este ejemplo si pega los cambios en el código de Compilar la aplicación o puede seguir las instrucciones de Descargar la aplicación para descargar el ejemplo y después ejecutar el proyecto QueueResults.

A continuación se muestra el resultado si el usuario elige el botón Inicio solo una vez. La etiqueta de letra, A, indica que el resultado es de la primera vez que se elige el botón Inicio. Los números muestran el orden de las direcciones URL en la lista de destinos de descarga.

#Starting group A.
#Task assigned for group A.

A-1. msdn.microsoft.com/library/hh191443.aspx                87389
A-2. msdn.microsoft.com/library/aa578028.aspx               209858
A-3. msdn.microsoft.com/library/jj155761.aspx                30870
A-4. msdn.microsoft.com/library/hh290140.aspx               119027
A-5. msdn.microsoft.com/library/hh524395.aspx                71260
A-6. msdn.microsoft.com/library/ms404677.aspx               199186
A-7. msdn.microsoft.com                                            53266
A-8. msdn.microsoft.com/library/ff730837.aspx               148020

TOTAL bytes returned:  918876


#Group A is complete.

Si el usuario elige el botón Inicio tres veces, la aplicación genera una salida que se parece a las líneas siguientes. Las líneas de información que comienzan con un signo de almohadilla (#) realizan un seguimiento del progreso de la aplicación.

#Starting group A.
#Task assigned for group A.

A-1. msdn.microsoft.com/library/hh191443.aspx                87389
A-2. msdn.microsoft.com/library/aa578028.aspx               207089
A-3. msdn.microsoft.com/library/jj155761.aspx                30870
A-4. msdn.microsoft.com/library/hh290140.aspx               119027
A-5. msdn.microsoft.com/library/hh524395.aspx                71259
A-6. msdn.microsoft.com/library/ms404677.aspx               199185

#Starting group B.
#Task assigned for group B.

A-7. msdn.microsoft.com                                            53266

#Starting group C.
#Task assigned for group C.

A-8. msdn.microsoft.com/library/ff730837.aspx               148010

TOTAL bytes returned:  916095

B-1. msdn.microsoft.com/library/hh191443.aspx                87389
B-2. msdn.microsoft.com/library/aa578028.aspx               207089
B-3. msdn.microsoft.com/library/jj155761.aspx                30870
B-4. msdn.microsoft.com/library/hh290140.aspx               119027
B-5. msdn.microsoft.com/library/hh524395.aspx                71260
B-6. msdn.microsoft.com/library/ms404677.aspx               199186

#Group A is complete.

B-7. msdn.microsoft.com                                            53266
B-8. msdn.microsoft.com/library/ff730837.aspx               148010

TOTAL bytes returned:  916097

C-1. msdn.microsoft.com/library/hh191443.aspx                87389
C-2. msdn.microsoft.com/library/aa578028.aspx               207089

#Group B is complete.

C-3. msdn.microsoft.com/library/jj155761.aspx                30870
C-4. msdn.microsoft.com/library/hh290140.aspx               119027
C-5. msdn.microsoft.com/library/hh524395.aspx                72765
C-6. msdn.microsoft.com/library/ms404677.aspx               199186
C-7. msdn.microsoft.com                                            56190
C-8. msdn.microsoft.com/library/ff730837.aspx               148010

TOTAL bytes returned:  920526

#Group C is complete.

Los grupos B y C comienzan antes de que el grupo A haya finalizado, pero salida de cada grupo aparece por separado. Toda la salida del grupo A ocurre primero, seguida por toda la salida del grupo B y, a continuación, toda la salida del grupo C. La aplicación muestra siempre los grupos en orden y, para cada grupo, muestra siempre la información sobre los sitios web individuales en el orden en que las direcciones URL aparecen en la lista correspondiente.

Sin embargo, no puede predecir el orden en el que las descargas se suceden realmente. Después de haber iniciado varios grupos, las tareas de descarga que generan están todas activas. No puede suponer que A-1 se descargará antes que B-1, y no puede suponer que A-1 se descargará antes que A-2.

Definiciones globales

El código de ejemplo contiene las dos declaraciones globales siguientes que son visibles desde todos los métodos.

Class MainWindow    ' Class MainPage in Windows Store app.

    ' ***Declare the following variables where all methods can access them.  
    Private pendingWork As Task = Nothing 
    Private group As Char = ChrW(AscW("A") - 1)
public partial class MainWindow : Window  // Class MainPage in Windows Store app.
{
    // ***Declare the following variables where all methods can access them.  
    private Task pendingWork = null;   
    private char group = (char)('A' - 1);

La variable Task, pendingWork, supervisa el proceso de presentación y evita que cualquier grupo interrumpa la operación de presentación de otro grupo. La variable de carácter, group, etiqueta el resultado de distintos grupos para comprobar que los resultados aparecen en el orden esperado.

El controlador de eventos Click

El controlador de eventos, StartButton_Click, incrementa la letra del grupo cada vez que el usuario elige el botón Inicio. Después el controlador llama a AccessTheWebAsync para ejecutar la operación de descarga.

Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)
    ' ***Verify that each group's results are displayed together, and that 
    ' the groups display in order, by marking each group with a letter.
    group = ChrW(AscW(group) + 1)
    ResultsTextBox.Text &= String.Format(vbCrLf & vbCrLf & "#Starting group {0}.", group)

    Try 
        ' *** Pass the group value to AccessTheWebAsync. 
        Dim finishedGroup As Char = Await AccessTheWebAsync(group)

        ' The following line verifies a successful return from the download and  
        ' display procedures. 
        ResultsTextBox.Text &= String.Format(vbCrLf & vbCrLf & "#Group {0} is complete." & vbCrLf, finishedGroup)

    Catch ex As Exception
        ResultsTextBox.Text &= vbCrLf & "Downloads failed." 

    End Try 
End Sub
private async void StartButton_Click(object sender, RoutedEventArgs e)
{
    // ***Verify that each group's results are displayed together, and that 
    // the groups display in order, by marking each group with a letter. 
    group = (char)(group + 1);
    ResultsTextBox.Text += string.Format("\r\n\r\n#Starting group {0}.", group);

    try
    {
        // *** Pass the group value to AccessTheWebAsync. 
        char finishedGroup = await AccessTheWebAsync(group);

        // The following line verifies a successful return from the download and 
        // display procedures. 
        ResultsTextBox.Text += string.Format("\r\n\r\n#Group {0} is complete.\r\n", finishedGroup);
    }
    catch (Exception)
    {
        ResultsTextBox.Text += "\r\nDownloads failed.";
    }
}

El método AccessTheWebAsync

Este ejemplo divide AccessTheWebAsync en dos métodos. El primer método, AccessTheWebAsync, inicia todas las tareas de descarga para un grupo y configura pendingWork para controlar el proceso de presentación. El método utiliza una consulta de Language-Integrated Query (LINQ) y ToArray``1 para iniciar todas las tareas de descarga de manera simultánea.

AccessTheWebAsync llama entonces a FinishOneGroupAsync para aguardar a la finalización de cada descarga y mostrar su longitud.

FinishOneGroupAsync devuelve una tarea asignada a pendingWork en AccessTheWebAsync. Ese valor evita la interrupción por otra operación antes de completar la tarea.

Private Async Function AccessTheWebAsync(grp As Char) As Task(Of Char)

    Dim client = New HttpClient()

    ' Make a list of the web addresses to download. 
    Dim urlList As List(Of String) = SetUpURLList()

    ' ***Kick off the downloads. The application of ToArray activates all the download tasks. 
    Dim getContentTasks As Task(Of Byte())() =
        urlList.Select(Function(addr) client.GetByteArrayAsync(addr)).ToArray()

    ' ***Call the method that awaits the downloads and displays the results. 
    ' Assign the Task that FinishOneGroupAsync returns to the gatekeeper task, pendingWork.
    pendingWork = FinishOneGroupAsync(urlList, getContentTasks, grp)

    ResultsTextBox.Text &=
        String.Format(vbCrLf & "#Task assigned for group {0}. Download tasks are active." & vbCrLf, grp)

    ' ***This task is complete when a group has finished downloading and displaying.
    Await pendingWork

    ' You can do other work here or just return. 
    Return grp
End Function
private async Task<char> AccessTheWebAsync(char grp)
{
    HttpClient client = new HttpClient();

    // Make a list of the web addresses to download.
    List<string> urlList = SetUpURLList();

    // ***Kick off the downloads. The application of ToArray activates all the download tasks.
    Task<byte[]>[] getContentTasks = urlList.Select(url => client.GetByteArrayAsync(url)).ToArray();

    // ***Call the method that awaits the downloads and displays the results. 
    // Assign the Task that FinishOneGroupAsync returns to the gatekeeper task, pendingWork.
    pendingWork = FinishOneGroupAsync(urlList, getContentTasks, grp);

    ResultsTextBox.Text += string.Format("\r\n#Task assigned for group {0}. Download tasks are active.\r\n", grp);

    // ***This task is complete when a group has finished downloading and displaying.
    await pendingWork;

    // You can do other work here or just return. 
    return grp;
}

El método FinishOneGroupAsync

Este método recorre las tareas de descarga de un grupo, espera a cada uno, muestra la longitud del sitio web descargado y agrega la longitud al total.

La primera instrucción en FinishOneGroupAsync utiliza pendingWork para asegurarse de que la especificación del método no interfiere con una operación que ya existe en el proceso de presentación o que ya está en espera. Si esta operación está en curso, la operación para escribir debe esperar su turno.

Private Async Function FinishOneGroupAsync(urls As List(Of String), contentTasks As Task(Of Byte())(), grp As Char) As Task

    ' Wait for the previous group to finish displaying results. 
    If pendingWork IsNot Nothing Then
        Await pendingWork
    End If 

    Dim total = 0

    ' contentTasks is the array of Tasks that was created in AccessTheWebAsync. 
    For i As Integer = 0 To contentTasks.Length - 1
        ' Await the download of a particular URL, and then display the URL and 
        ' its length. 
        Dim content As Byte() = Await contentTasks(i)
        DisplayResults(urls(i), content, i, grp)
        total += content.Length
    Next 

    ' Display the total count for all of the websites.
    ResultsTextBox.Text &=
        String.Format(vbCrLf & vbCrLf & "TOTAL bytes returned:  " & total & vbCrLf)
End Function
private async Task FinishOneGroupAsync(List<string> urls, Task<byte[]>[] contentTasks, char grp)
{
    // ***Wait for the previous group to finish displaying results. 
    if (pendingWork != null) await pendingWork;

    int total = 0;

    // contentTasks is the array of Tasks that was created in AccessTheWebAsync. 
    for (int i = 0; i < contentTasks.Length; i++)
    {
        // Await the download of a particular URL, and then display the URL and 
        // its length. 
        byte[] content = await contentTasks[i];
        DisplayResults(urls[i], content, i, grp);
        total += content.Length;
    }

    // Display the total count for all of the websites.
    ResultsTextBox.Text +=
        string.Format("\r\n\r\nTOTAL bytes returned:  {0}\r\n", total);
}

Puede ejecutar este ejemplo si pega los cambios en el código de Compilar la aplicación o puede seguir las instrucciones de Descargar la aplicación para descargar el ejemplo y después ejecutar el proyecto QueueResults.

Puntos de interés

Las líneas de información que comienzan con un signo de almohadilla (#) en el resultado aclaran cómo funciona este ejemplo.

El resultado siguiente muestra los siguientes patrones.

  • Un grupo se puede iniciar mientras se muestra el resultado de un grupo anterior, pero la visualización de la salida del grupo anterior no se interrumpe.

    #Starting group A.
    #Task assigned for group A. Download tasks are active.
    
    A-1. msdn.microsoft.com/library/hh191443.aspx                87389
    A-2. msdn.microsoft.com/library/aa578028.aspx               207089
    A-3. msdn.microsoft.com/library/jj155761.aspx                30870
    A-4. msdn.microsoft.com/library/hh290140.aspx               119037
    A-5. msdn.microsoft.com/library/hh524395.aspx                71260
    
    #Starting group B.
    #Task assigned for group B. Download tasks are active.
    
    A-6. msdn.microsoft.com/library/ms404677.aspx               199186
    A-7. msdn.microsoft.com                                            53078
    A-8. msdn.microsoft.com/library/ff730837.aspx               148010
    
    TOTAL bytes returned:  915919
    
    B-1. msdn.microsoft.com/library/hh191443.aspx                87388
    B-2. msdn.microsoft.com/library/aa578028.aspx               207089
    B-3. msdn.microsoft.com/library/jj155761.aspx                30870
    
    #Group A is complete.
    
    B-4. msdn.microsoft.com/library/hh290140.aspx               119027
    B-5. msdn.microsoft.com/library/hh524395.aspx                71260
    B-6. msdn.microsoft.com/library/ms404677.aspx               199186
    B-7. msdn.microsoft.com                                            53078
    B-8. msdn.microsoft.com/library/ff730837.aspx               148010
    
    TOTAL bytes returned:  915908
    
  • La tarea pendingWork es null (Nothing en Visual Basic) al principio de FinishOneGroupAsync solo para el grupo A, que se inició primero. El grupo A aún no ha completado una expresión await cuando alcanza FinishOneGroupAsync. Por consiguiente, el control no ha vuelto a AccessTheWebAsync y la primera asignación a pendingWork no se ha producido.

  • Las dos líneas siguientes siempre aparecen juntas en la salida. El código nunca se interrumpe entre el inicio de una operación de grupo en StartButton_Click y la asignación de una tarea para el grupo en pendingWork.

    #Starting group B.
    #Task assigned for group B. Download tasks are active.
    

    Después de que un grupo entre en StartButton_Click, la operación no completa una expresión de aguardar hasta que la operación entre en FinishOneGroupAsync. Por consiguiente, ninguna otra operación puede obtener el control durante ese segmento de código.

Revisar y ejecutar la aplicación de ejemplo

Para entender mejor la aplicación de ejemplo, puede descargarla, compilarla manualmente o revisar el código al final de este tema sin implementar la aplicación.

Nota

Para ejecutar el ejemplo como una aplicación de escritorio de Windows Presentation Foundation (WPF), debe tener Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 para escritorio de Windows, Visual Studio Express 2013 para Windows o .NET Framework 4.5 o 4.5.1 instalado en su equipo.

Para ejecutar el ejemplo como una aplicación Tienda Windows, debe tener Windows 8 instalado en el equipo.Además, si desea ejecutar el ejemplo desde Visual Studio, también debe tener instalados Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 para Windows 8 o Visual Studio Express 2013 para Windows.Visual Studio 2010 no puede cargar los proyectos que van destinados a .NET Framework 4.5.

Descargar la aplicación

  1. Descargue el archivo comprimido de Async Samples: Reentrancy in .NET Desktop Apps o de los Async Samples: Reentrancy in Windows Store Apps.

  2. Descomprima el archivo que ha descargado y, a continuación, inicie Visual Studio.

  3. En la barra de menú, elija Archivo, Abrir, Proyecto/Solución.

  4. Navegue a la carpeta que contiene el código de ejemplo descomprimido y abra el archivo de solución (.sln).

  5. En el Explorador de soluciones, abra el acceso directo del proyecto que desea ejecutar y, a continuación, elija Establecer como proyecto de inicio.

  6. Elija las teclas CTRL+F5 para compilar y ejecutar el proyecto.

Compilar la aplicación

Las secciones siguientes proporcionan el código para compilar el ejemplo como aplicación WPF o Tienda Windows.

Para compilar una aplicación WPF

  1. Inicie Visual Studio.

  2. En la barra de menús, elija Archivo, Nuevo, Proyecto.

    Aparece el cuadro de diálogo Nuevo proyecto.

  3. En el panel Plantillas instaladas, expanda Visual Basic o Visual C# y, a continuación, expanda Windows.

  4. En la lista de tipos de proyectos, elija Aplicación WPF.

  5. Denomine WebsiteDownloadWPF al proyecto y, a continuación, elija el botón Aceptar.

    El nuevo proyecto aparecerá en el Explorador de soluciones.

  6. En el Editor de código de Visual Studio, elija la pestaña MainWindow.xaml.

    Si la pestaña no está visible, abra el acceso directo de MainWindow.xaml en el Explorador de soluciones y, a continuación, elija Ver código.

  7. En la vista XAML MainWindow.xaml, reemplace el código por el código siguiente.

    <Window x:Class="MainWindow"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:WebsiteDownloadWPF"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
        <Grid Width="517" Height="360">
            <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="-1,0,0,0" VerticalAlignment="Top" Click="StartButton_Click" Height="53" Background="#FFA89B9B" FontSize="36" Width="518"  />
            <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" Margin="-1,53,0,-36" TextWrapping="Wrap" VerticalAlignment="Top" Height="343" FontSize="10" ScrollViewer.VerticalScrollBarVisibility="Visible" Width="518" FontFamily="Lucida Console" />
        </Grid>
    </Window>
    
    <Window x:Class="WebsiteDownloadWPF.MainWindow"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:WebsiteDownloadWPF"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d">
    
        <Grid Width="517" Height="360">
            <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="-1,0,0,0" VerticalAlignment="Top" Click="StartButton_Click" Height="53" Background="#FFA89B9B" FontSize="36" Width="518"  />
            <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" Margin="-1,53,0,-36" TextWrapping="Wrap" VerticalAlignment="Top" Height="343" FontSize="10" ScrollViewer.VerticalScrollBarVisibility="Visible" Width="518" FontFamily="Lucida Console" />
        </Grid>
    </Window>
    

    Una ventana simple que contiene un cuadro de texto y un botón aparece en la vista Diseño de MainWindow.xaml.

  8. Agregue una referencia para System.Net.Http.

  9. En el Explorador de soluciones, abra el acceso directo de MainWindow.xaml.vb o MainWindow.xaml.cs y, a continuación, elija Código de la vista.

  10. Reemplace el código de MainWindow.xaml.vb o MainWindow.xaml.cs por el código siguiente.

    ' Add the following Imports statements, and add a reference for System.Net.Http. 
    Imports System.Net.Http
    Imports System.Threading
    
    Class MainWindow
    
        Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)
            ' This line is commented out to make the results clearer in the output. 
            'ResultsTextBox.Text = "" 
    
            Try
                Await AccessTheWebAsync()
    
            Catch ex As Exception
                ResultsTextBox.Text &= vbCrLf & "Downloads failed." 
    
            End Try 
        End Sub 
    
    
        Private Async Function AccessTheWebAsync() As Task
    
            ' Declare an HttpClient object. 
            Dim client = New HttpClient()
    
            ' Make a list of web addresses. 
            Dim urlList As List(Of String) = SetUpURLList()
    
            Dim total = 0
            Dim position = 0
    
            For Each url In urlList
                ' GetByteArrayAsync returns a task. At completion, the task 
                ' produces a byte array. 
                Dim urlContents As Byte() = Await client.GetByteArrayAsync(url)
    
                position += 1
                DisplayResults(url, urlContents, position)
    
                ' Update the total.
                total += urlContents.Length
            Next 
    
            ' Display the total count for all of the websites.
            ResultsTextBox.Text &=
                String.Format(vbCrLf & vbCrLf & "TOTAL bytes returned:  " & total & vbCrLf)
        End Function 
    
    
        Private Function SetUpURLList() As List(Of String)
            Dim urls = New List(Of String) From
            {
                "https://msdn.microsoft.com/en-us/library/hh191443.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/jj155761.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "https://msdn.microsoft.com/en-us/library/hh524395.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            }
            Return urls
        End Function 
    
    
        Private Sub DisplayResults(url As String, content As Byte(), pos As Integer)
            ' Display the length of each website. The string format is designed 
            ' to be used with a monospaced font, such as Lucida Console or 
            ' Global Monospace. 
    
            ' Strip off the "http:'". 
            Dim displayURL = url.Replace("http://", "")
            ' Display position in the URL list, the URL, and the number of bytes.
            ResultsTextBox.Text &= String.Format(vbCrLf & "{0}. {1,-58} {2,8}", pos, displayURL, content.Length)
        End Sub 
    End Class
    
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Documents;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Imaging;
    using System.Windows.Navigation;
    using System.Windows.Shapes;
    
    // Add the following using directives, and add a reference for System.Net.Http. 
    using System.Net.Http;
    using System.Threading;
    
    namespace WebsiteDownloadWPF
    {
        public partial class MainWindow : Window
        {
            public MainWindow()
            {
                InitializeComponent();
            }
    
    
            private async void StartButton_Click(object sender, RoutedEventArgs e)
            {
                // This line is commented out to make the results clearer in the output. 
                //ResultsTextBox.Text = "";
    
                try
                {
                    await AccessTheWebAsync();
                }
                catch (Exception)
                {
                    ResultsTextBox.Text += "\r\nDownloads failed.";
                }
            }
    
    
            private async Task AccessTheWebAsync()
            {
                // Declare an HttpClient object.
                HttpClient client = new HttpClient();
    
                // Make a list of web addresses.
                List<string> urlList = SetUpURLList();
    
                var total = 0;
                var position = 0;
    
                foreach (var url in urlList)
                {
                    // GetByteArrayAsync returns a task. At completion, the task 
                    // produces a byte array. 
                    byte[] urlContents = await client.GetByteArrayAsync(url);
    
                    DisplayResults(url, urlContents, ++position);
    
                    // Update the total.
                    total += urlContents.Length;
                }
    
                // Display the total count for all of the websites.
                ResultsTextBox.Text +=
                    string.Format("\r\n\r\nTOTAL bytes returned:  {0}\r\n", total);
            }
    
    
            private List<string> SetUpURLList()
            {
                List<string> urls = new List<string> 
                { 
                    "https://msdn.microsoft.com/en-us/library/hh191443.aspx",
                    "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                    "https://msdn.microsoft.com/en-us/library/jj155761.aspx",
                    "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                    "https://msdn.microsoft.com/en-us/library/hh524395.aspx",
                    "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                    "https://msdn.microsoft.com",
                    "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
                };
                return urls;
            }
    
    
            private void DisplayResults(string url, byte[] content, int pos)
            {
                // Display the length of each website. The string format is designed 
                // to be used with a monospaced font, such as Lucida Console or  
                // Global Monospace. 
    
                // Strip off the "http://".
                var displayURL = url.Replace("http://", "");
                // Display position in the URL list, the URL, and the number of bytes.
                ResultsTextBox.Text += string.Format("\n{0}. {1,-58} {2,8}", pos, displayURL, content.Length);
            }
        }
    }
    
  11. Elija las teclas CTRL+F5 para ejecutar el programa y, a continuación, el botón Iniciar varias veces.

  12. Realice los cambios desde Deshabilitar el botón Iniciar, Cancelar y reiniciar la operación, o Ejecutar varias operaciones y poner en cola el resultado para controlar la nueva entrada.

Para compilar una aplicación de la Tienda Windows

  1. Inicie Visual Studio.

  2. En la barra de menús, elija Archivo, Nuevo, Proyecto.

    Aparece el cuadro de diálogo Nuevo proyecto.

  3. En Instalado, categoría de Plantillas, expanda Visual Basic o Visual C# y, a continuación, expanda Tienda Windows.

  4. En la lista de tipos de proyecto, elija Aplicación vacía (XAML).

  5. Denomine WebsiteDownloadWin al proyecto y, a continuación, elija el botón Aceptar.

    El nuevo proyecto aparecerá en el Explorador de soluciones.

  6. En el Explorador de soluciones, abra el menú contextual para MainPage.xaml y, a continuación, elija Abrir.

  7. En la ventana XAML de MainPage.xaml, reemplace el código por el código siguiente.

    <Page
        x:Class="WebsiteDownloadWin.MainPage"
        xmlns="https://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="https://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:WebsiteDownloadWin"
        xmlns:d="https://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="https://schemas.openxmlformats.org/markup-compatibility/2006"
        mc:Ignorable="d" FontSize="12">
    
        <Grid Background="{StaticResource ApplicationPageBackgroundThemeBrush}">
            <Button x:Name="StartButton" Content="Start" HorizontalAlignment="Left" Margin="325,77,0,0" VerticalAlignment="Top" Click="StartButton_Click" Height="145" Background="#FFA89B9B" FontSize="36" Width="711"  />
            <TextBox x:Name="ResultsTextBox" HorizontalAlignment="Left" Margin="325,222,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Height="546" FontSize="10" ScrollViewer.VerticalScrollBarVisibility="Visible" Width="711" FontFamily="Lucida Console" />
        </Grid>
    </Page>
    

    Una sencilla ventana que contiene un cuadro de texto y un botón Inicio aparece en la ventana Diseño de MainPage.xaml.

  8. En el Explorador de soluciones, abra el menú contextual de MainPage.xaml.vb o MainPage.xaml.cs y, a continuación, elija Código de la vista.

  9. Reemplace el código de MainPage.xaml.vb o MainPage.xaml.cs por el código siguiente.

    ' Add the following Imports statements. 
    Imports System.Threading.Tasks
    Imports System.Threading
    Imports System.Net.Http
    
    Public NotInheritable Class MainPage
        Inherits Page
    
        Protected Overrides Sub OnNavigatedTo(e As Navigation.NavigationEventArgs)
        End Sub 
    
    
        Private Async Sub StartButton_Click(sender As Object, e As RoutedEventArgs)
            ' This line is commented out to make the results clearer in the output. 
            'ResultsTextBox.Text = "" 
    
            Try
                Await AccessTheWebAsync()
    
            Catch ex As Exception
                ResultsTextBox.Text &= vbCrLf & "Downloads failed." 
    
            End Try 
        End Sub 
    
    
        Private Async Function AccessTheWebAsync() As Task
    
            ' Declare an HttpClient object. 
            Dim client = New HttpClient()
    
            ' Make a list of web addresses. 
            Dim urlList As List(Of String) = SetUpURLList()
    
            Dim total = 0
            Dim position = 0
    
            For Each url In urlList
                ' GetByteArrayAsync returns a task. At completion, the task 
                ' produces a byte array. 
                Dim urlContents As Byte() = Await client.GetByteArrayAsync(url)
    
                position += 1
                DisplayResults(url, urlContents, position)
    
                ' Update the total.
                total += urlContents.Length
            Next 
    
            ' Display the total count for all of the websites.
            ResultsTextBox.Text &=
                String.Format(vbCrLf & vbCrLf & "TOTAL bytes returned:  " & total & vbCrLf)
        End Function 
    
    
        Private Function SetUpURLList() As List(Of String)
            Dim urls = New List(Of String) From
            {
                "https://msdn.microsoft.com/en-us/library/hh191443.aspx",
                "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                "https://msdn.microsoft.com/en-us/library/jj155761.aspx",
                "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                "https://msdn.microsoft.com/en-us/library/hh524395.aspx",
                "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                "https://msdn.microsoft.com",
                "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
            }
            Return urls
        End Function 
    
    
        Private Sub DisplayResults(url As String, content As Byte(), pos As Integer)
            ' Display the length of each website. The string format is designed 
            ' to be used with a monospaced font, such as Lucida Console or 
            ' Global Monospace. 
    
            ' Strip off the "http:'". 
            Dim displayURL = url.Replace("http://", "")
            ' Display position in the URL list, the URL, and the number of bytes.
            ResultsTextBox.Text &= String.Format(vbCrLf & "{0}. {1,-58} {2,8}", pos, displayURL, content.Length)
        End Sub 
    End Class
    
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Linq;
    using Windows.Foundation;
    using Windows.Foundation.Collections;
    using Windows.UI.Xaml;
    using Windows.UI.Xaml.Controls;
    using Windows.UI.Xaml.Controls.Primitives;
    using Windows.UI.Xaml.Data;
    using Windows.UI.Xaml.Input;
    using Windows.UI.Xaml.Media;
    using Windows.UI.Xaml.Navigation;
    
    // Add the following using directives.  
    using System.Threading.Tasks;
    using System.Threading;
    using System.Net.Http;
    
    
    namespace WebsiteDownloadWin
    {
        public sealed partial class MainPage : Page
        {
            public MainPage()
            {
                this.InitializeComponent();
            }
    
            private async void StartButton_Click(object sender, RoutedEventArgs e)
            {
                // This line is commented out to make the results clearer in the output. 
                //ResultsTextBox.Text = "";
    
                try
                {
                    await AccessTheWebAsync();
                }
                catch (Exception)
                {
                    ResultsTextBox.Text += "\r\nDownloads failed.";
                }
            }
    
    
            private async Task AccessTheWebAsync()
            {
                // Declare an HttpClient object.
                HttpClient client = new HttpClient();
    
                // Make a list of web addresses.
                List<string> urlList = SetUpURLList();
    
                var total = 0;
                var position = 0;
    
                foreach (var url in urlList)
                {
                    // GetByteArrayAsync returns a task. At completion, the task 
                    // produces a byte array. 
                    byte[] urlContents = await client.GetByteArrayAsync(url);
    
                    DisplayResults(url, urlContents, ++position);
    
                    // Update the total.
                    total += urlContents.Length;
                }
    
                // Display the total count for all of the websites.
                ResultsTextBox.Text +=
                    string.Format("\r\n\r\nTOTAL bytes returned:  {0}\r\n", total);
            }
    
    
            private List<string> SetUpURLList()
            {
                List<string> urls = new List<string> 
                { 
                    "https://msdn.microsoft.com/en-us/library/hh191443.aspx",
                    "https://msdn.microsoft.com/en-us/library/aa578028.aspx",
                    "https://msdn.microsoft.com/en-us/library/jj155761.aspx",
                    "https://msdn.microsoft.com/en-us/library/hh290140.aspx",
                    "https://msdn.microsoft.com/en-us/library/hh524395.aspx",
                    "https://msdn.microsoft.com/en-us/library/ms404677.aspx",
                    "https://msdn.microsoft.com",
                    "https://msdn.microsoft.com/en-us/library/ff730837.aspx"
                };
                return urls;
            }
    
    
            private void DisplayResults(string url, byte[] content, int pos)
            {
                // Display the length of each website. The string format is designed 
                // to be used with a monospaced font, such as Lucida Console or 
                // Global Monospace. 
    
                // Strip off the "http://".
                var displayURL = url.Replace("http://", "");
                // Display position in the URL list, the URL, and the number of bytes.
                ResultsTextBox.Text += string.Format("\n{0}. {1,-58} {2,8}", pos, displayURL, content.Length);
            }
        }
    }
    
  10. Elija las teclas CTRL+F5 para ejecutar el programa y, a continuación, el botón Iniciar varias veces.

  11. Realice los cambios desde Deshabilitar el botón Iniciar, Cancelar y reiniciar la operación, o Ejecutar varias operaciones y poner en cola el resultado para controlar la nueva entrada.

Vea también

Tareas

Walkthrough: Acceso a web usando Async y Await (C# y Visual Basic)

Conceptos

Programación asincrónica con Async y Await (C# y Visual Basic)

Otros recursos

Programación asincrónica (aplicaciones de la Tienda Windows)

Tutorial rápido: Llamada a API asincrónica en C# o Visual Basic