Controlar la reentrada en aplicaciones asincrónicas (C#)Handling Reentrancy in Async Apps (C#)

Cuando se incluye código asincrónico en una aplicación, hay que tener en cuenta (y posiblemente evitar) la reentrada, que significa volver a especificar una operación asincrónica antes de que finalice.When you include asynchronous code in your app, you should consider and possibly prevent reentrancy, which refers to reentering an asynchronous operation before it has completed. Si no se identifican ni controlan las posibilidades de reentrada, pueden producirse resultados inesperados.If you don't identify and handle possibilities for reentrancy, it can cause unexpected results.

En este temaIn this topic

Nota

Para ejecutar el ejemplo, debe tener instalado en el equipo Visual Studio 2012 o posterior y .NET Framework 4.5 o posterior.To run the example, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

Reconocer la reentradaRecognizing Reentrancy

En el ejemplo de este tema, los usuarios hacen clic en un botón Start (Iniciar) para iniciar una aplicación asincrónica que descarga una serie de sitios web y calcula el número total de bytes que se descargan.In the example in this topic, users choose a Start button to initiate an asynchronous app that downloads a series of websites and calculates the total number of bytes that are downloaded. Una versión sincrónica del ejemplo respondería de la misma forma independientemente de cuántas veces un usuario elija el botón porque, tras la primera vez, el subproceso de UI omite esos eventos hasta que finaliza la ejecución de la aplicación.A synchronous version of the example would respond the same way regardless of how many times a user chooses the button because, after the first time, the UI thread ignores those events until the app finishes running. Sin embargo, en una aplicación asincrónica, el subproceso de UI continúa respondiendo y podría volver a introducir la operación asincrónica antes de que finalice.In an asynchronous app, however, the UI thread continues to respond, and you might reenter the asynchronous operation before it has completed.

En el ejemplo siguiente se muestra la salida esperada si el usuario hace clic en el botón Start una sola vez.The following example shows the expected output if the user chooses the Start button only once. Aparece una lista de los sitios web descargados con el tamaño, en bytes, de cada sitio.A list of the downloaded websites appears with the size, in bytes, of each site. El número total de bytes aparece al final.The total number of bytes appears at the end.

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, el controlador de eventos se invoca repetidamente y el proceso de descarga se vuelve a introducir cada vez.However, if the user chooses the button more than once, the event handler is invoked repeatedly, and the download process is reentered each time. Como resultado, se ejecutan varias operaciones asincrónicas al mismo tiempo, la salida intercala los resultados y el número total de bytes es confuso.As a result, several asynchronous operations are running at the same time, the output interleaves the results, and the total number of bytes is confusing.

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

Al final de este tema puede revisar el código que genera este resultado.You can review the code that produces this output by scrolling to the end of this topic. Si quiere experimentar con el código, descargue la solución en el equipo local y ejecute el proyecto WebsiteDownload o use el código que aparece al final de este tema para crear su propio proyecto.You can experiment with the code by downloading the solution to your local computer and then running the WebsiteDownload project or by using the code at the end of this topic to create your own project. Para más información e instrucciones, vea Revisión y ejecución de la aplicación de ejemplo.For more information and instructions, see Reviewing and Running the Example App.

Controlar la reentradaHandling Reentrancy

La reentrada se puede controlar de varias maneras en función de lo que se desee de la aplicación.You can handle reentrancy in a variety of ways, depending on what you want your app to do. Este tema presenta los siguientes ejemplos:This topic presents the following examples:

  • Deshabilitar el botón de inicioDisable the Start Button

    Deshabilite el botón Start (Iniciar) mientras se ejecuta la operación de modo que el usuario no pueda interrumpirla.Disable the Start button while the operation is running so that the user can't interrupt it.

  • Cancelar y reiniciar la operaciónCancel and Restart the Operation

    Cancele cualquier operación que se esté ejecutando cuando el usuario haga clic de nuevo en el botón Start y, después, deje que continúe la última operación solicitada.Cancel any operation that is still running when the user chooses the Start button again, and then let the most recently requested operation continue.

  • Ejecutar varias operaciones y poner en cola la salidaRun Multiple Operations and Queue the Output

    Permita que todas las operaciones solicitadas se ejecuten de forma asincrónica, pero coordine la presentación de salida para que los resultados de cada operación aparecen juntos y en orden.Allow all requested operations to run asynchronously, but coordinate the display of output so that the results from each operation appear together and in order.

Deshabilitar el botón de inicioDisable the Start Button

Puede bloquear el botón Start mientras se ejecuta una operación si lo deshabilita en la parte superior del controlador de eventos StartButton_Click.You can block the Start button while an operation is running by disabling the button at the top of the StartButton_Click event handler. A continuación, cuando finalice la operación, puede habilitar de nuevo el botón desde un bloque finally de modo que los usuarios puedan volver a ejecutar la aplicación.You can then reenable the button from within a finally block when the operation finishes so that users can run the app again.

Para configurar este escenario, haga los cambios siguientes en el código básico que se proporciona en Revisión y ejecución de la aplicación de ejemplo.To set up this scenario, make the following changes to the basic code that is provided in Reviewing and Running the Example App. También puede descargar la aplicación finalizada de Async Samples: Reentrancy in .NET Desktop Apps (Ejemplos asincrónicos: reentrada en aplicaciones de escritorio de .NET).You also can download the finished app from Async Samples: Reentrancy in .NET Desktop Apps. El nombre del proyecto es DisableStartButton.The name of the project is DisableStartButton.

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 AccessTheWebAsync está descargando los sitios web, por lo que no se puede volver a introducir el proceso.As a result of the changes, the button doesn't respond while AccessTheWebAsync is downloading the websites, so the process can’t be reentered.

Cancelar y reiniciar la operaciónCancel and Restart the Operation

En lugar de deshabilitar el botón Start, puede mantenerlo activo y, si el usuario vuelve a seleccionarlo, cancelar la operación que ya se está ejecutando y permitir que la última operación iniciada continúe.Instead of disabling the Start button, you can keep the button active but, if the user chooses that button again, cancel the operation that's already running and let the most recently started operation continue.

Para más información sobre la cancelación, vea Fine-Tuning Your Async Application (C#) (Ajustar la aplicación asincrónica [C#]).For more information about cancellation, see Fine-Tuning Your Async Application (C#).

Para configurar este escenario, haga los cambios siguientes en el código básico que se proporciona en Revisión y ejecución de la aplicación de ejemplo.To set up this scenario, make the following changes to the basic code that is provided in Reviewing and Running the Example App. También puede descargar la aplicación finalizada de Async Samples: Reentrancy in .NET Desktop Apps (Ejemplos asincrónicos: reentrada en aplicaciones de escritorio de .NET).You also can download the finished app from Async Samples: Reentrancy in .NET Desktop Apps. El nombre del proyecto es CancelAndRestart.The name of the project is CancelAndRestart.

  1. Declare una variable de CancellationTokenSource, cts, que esté en el ámbito de todos los métodos.Declare a CancellationTokenSource variable, cts, that’s in scope for all methods.

    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 está en curso.In StartButton_Click, determine whether an operation is already underway. Si el valor de cts es NULL, ya no hay ninguna operación activa.If the value of cts is null, no operation is already active. Si el valor no es null, se cancela la operación que se esté ejecutando.If the value isn't null, the operation that is already running is canceled.

    // *** If a download process is already underway, cancel it.
    if (cts != null)
    {
        cts.Cancel();
    }
    
  3. Establezca cts en un valor diferente que represente el proceso actual.Set cts to a different value that represents the current process.

    // *** 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, el proceso actual está completo, así que vuelva a establecer el valor de cts en null.At the end of StartButton_Click, the current process is complete, so set the value of cts back to null.

    // *** 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.The following code shows all the changes in StartButton_Click. Las adiciones se marcan con asteriscos.The additions are marked with asterisks.

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 siguientes cambios.In AccessTheWebAsync, make the following changes.

  • Agregue un parámetro para aceptar el token de cancelación de StartButton_Click.Add a parameter to accept the cancellation token from StartButton_Click.

  • Use el método GetAsync para descargar los sitios web porque GetAsync acepta un argumento CancellationToken.Use the GetAsync method to download the websites because GetAsync accepts a CancellationToken argument.

  • Antes de llamar a DisplayResults para mostrar los resultados de los sitios web descargados, revise ct para comprobar que no se ha cancelado la operación actual.Before calling DisplayResults to display the results for each downloaded website, check ct to verify that the current operation hasn’t been canceled.

El código siguiente muestra estos cambios marcados con asteriscos.The following code shows these changes, which are marked with asterisks.

// *** 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 +=
        $"\r\n\r\nTOTAL bytes returned:  {total}\r\n";
}

Si hace clic varias veces en el botón Start mientras se ejecuta esta aplicación, debería producir resultados similares a la salida siguiente.If you choose the Start button several times while this app is running, it should produce results that resemble the following output.

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 las 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 reinicie la operación.To eliminate the partial lists, uncomment the first line of code in StartButton_Click to clear the text box each time the user restarts the operation.

Ejecutar varias operaciones y poner en cola el resultadoRun Multiple Operations and Queue the Output

Este tercer ejemplo es el más complicado porque la aplicación inicia otra operación asincrónica cada vez que el usuario selecciona el botón Start y todas las operaciones se ejecutan hasta completarse.This third example is the most complicated in that the app starts another asynchronous operation each time that the user chooses the Start button, and all the operations run to completion. Todas las operaciones solicitadas descargan los sitios web de la lista de forma asincrónica, pero la salida de las operaciones se presenta de manera secuencial.All the requested operations download websites from the list asynchronously, but the output from the operations is presented sequentially. Es decir, la actividad de descarga real se intercala, según se muestra en la salida de Reconocer la reentrada, pero la lista de resultados de cada grupo se presenta por separado.That is, the actual downloading activity is interleaved, as the output in Recognizing Reentrancy shows, but the list of results for each group is presented separately.

Las operaciones comparten una Task global, pendingWork, que actúa de equipo selector para el proceso de visualización.The operations share a global Task, pendingWork, which serves as a gatekeeper for the display process.

Para configurar este escenario, haga los cambios siguientes en el código básico que se proporciona en Revisión y ejecución de la aplicación de ejemplo.To set up this scenario, make the following changes to the basic code that is provided in Reviewing and Running the Example App. También puede descargar la aplicación finalizada de Async Samples: Reentrancy in .NET Desktop Apps (Ejemplos asincrónicos: reentrada en aplicaciones de escritorio de .NET).You also can download the finished app from Async Samples: Reentrancy in .NET Desktop Apps. El nombre del proyecto es QueueResults.The name of the project is QueueResults.

En la salida siguiente se muestra el resultado cuando el usuario selecciona el botón Start una sola vez.The following output shows the result if the user chooses the Start button only once. La etiqueta de letra A indica que el resultado se corresponde a la primera vez que se selecciona el botón Start.The letter label, A, indicates that the result is from the first time the Start button is chosen. Los números muestran el orden de las direcciones URL en la lista de destinos de descarga.The numbers show the order of the URLs in the list of download targets.

#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 hace clic tres veces en el botón Start, la aplicación genera una salida similar a las líneas siguientes.If the user chooses the Start button three times, the app produces output that resembles the following lines. Las líneas de información que comienzan con una almohadilla (#) siguen el progreso de la aplicación.The information lines that start with a pound sign (#) trace the progress of the application.

#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 se inician antes de finalizar el grupo A, pero la salida de cada grupo aparece por separado.Groups B and C start before group A has finished, but the output for the each group appears separately. Primero aparece toda la salida del grupo A, seguida de la salida del grupo B y después la del grupo C. La aplicación siempre muestra los grupos en orden y, en cada grupo, muestra la información sobre los sitios web individuales en el orden en que las direcciones URL aparecen en la lista de direcciones URL.All the output for group A appears first, followed by all the output for group B, and then all the output for group C. The app always displays the groups in order and, for each group, always displays the information about the individual websites in the order that the URLs appear in the list of URLs.

Sin embargo, no es posible predecir el orden en que se producen las descargas.However, you can't predict the order in which the downloads actually happen. Después de iniciarse varios grupos, las tareas de descarga que generan están activas.After multiple groups have been started, the download tasks that they generate are all active. No se puede dar por sentado que A-1 se descargará antes que B-1, ni que A-1 se descargará antes que A-2.You can't assume that A-1 will be downloaded before B-1, and you can't assume that A-1 will be downloaded before A-2.

Definiciones globalesGlobal Definitions

El código de ejemplo contiene las dos declaraciones globales siguientes que están visibles en todos los métodos.The sample code contains the following two global declarations that are visible from all methods.

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 de Task, pendingWork, supervisa el proceso de presentación e impide que un grupo interrumpa la operación de presentación de otro grupo.The Task variable, pendingWork, oversees the display process and prevents any group from interrupting another group's display operation. La variable de caracteres, group, etiqueta la salida de diferentes grupos para comprobar que los resultados aparecen en el orden esperado.The character variable, group, labels the output from different groups to verify that results appear in the expected order.

El controlador de eventos ClickThe Click Event Handler

El controlador de eventos, StartButton_Click, incrementa la letra del grupo cada vez que el usuario selecciona el botón Start.The event handler, StartButton_Click, increments the group letter each time the user chooses the Start button. A continuación, el controlador llama a AccessTheWebAsync para ejecutar la operación de descarga.Then the handler calls AccessTheWebAsync to run the downloading operation.

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 += $"\r\n\r\n#Starting group {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 += $"\r\n\r\n#Group {finishedGroup} is complete.\r\n";
    }
    catch (Exception)
    {
        ResultsTextBox.Text += "\r\nDownloads failed.";
    }
}

El método AccessTheWebAsyncThe AccessTheWebAsync Method

En este ejemplo se divide AccessTheWebAsync en dos métodos.This example splits AccessTheWebAsync into two methods. El primer método, AccessTheWebAsync, inicia todas las tareas de descarga de un grupo y configura pendingWork para controlar el proceso de visualización.The first method, AccessTheWebAsync, starts all the download tasks for a group and sets up pendingWork to control the display process. El método usa una consulta de Language Integrated Query (consulta LINQ) y ToArray para iniciar todas las tareas de descarga al mismo tiempo.The method uses a Language Integrated Query (LINQ query) and ToArray to start all the download tasks at the same time.

A continuación, AccessTheWebAsync llama a FinishOneGroupAsync para esperar la finalización de todas las descargas y mostrar su duración.AccessTheWebAsync then calls FinishOneGroupAsync to await the completion of each download and display its length.

FinishOneGroupAsync devuelve una tarea que se asigna a pendingWork en AccessTheWebAsync.FinishOneGroupAsync returns a task that's assigned to pendingWork in AccessTheWebAsync. Ese valor evita que otra operación interrumpa la tarea antes de que finalice.That value prevents interruption by another operation before the task is complete.

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 += $"\r\n#Task assigned for group {grp}. Download tasks are active.\r\n";

    // ***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 FinishOneGroupAsyncThe FinishOneGroupAsync Method

Este método recorre las tareas de descarga de un grupo, espera por cada una de ellas, muestra la longitud del sitio web descargado y agrega la longitud total.This method cycles through the download tasks in a group, awaiting each one, displaying the length of the downloaded website, and adding the length to the total.

La primera instrucción de FinishOneGroupAsync usa pendingWork para asegurarse de que la entrada al método no interfiere con una operación que ya está en el proceso de visualización o que ya está esperando.The first statement in FinishOneGroupAsync uses pendingWork to make sure that entering the method doesn't interfere with an operation that is already in the display process or that's already waiting. Si tal operación está en curso, la operación introducida debe esperar su turno.If such an operation is in progress, the entering operation must wait its turn.

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 +=
        $"\r\n\r\nTOTAL bytes returned:  {total}\r\n";
}

Puntos de interésPoints of Interest

Las líneas de información que comienzan con un signo de almohadilla (#) en la salida aclaran cómo funciona este ejemplo.The information lines that start with a pound sign (#) in the output clarify how this example works.

La salida muestra los siguientes patrones.The output shows the following patterns.

  • Un grupo puede iniciarse mientras un grupo anterior muestra su salida, pero la visualización del grupo anterior no se ve interrumpida.A group can be started while a previous group is displaying its output, but the display of the previous group's output isn't interrupted.

    #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 al inicio de FinishOneGroupAsync solo para el grupo A, que fue el primero en empezar.The pendingWork task is null at the start of FinishOneGroupAsync only for group A, which started first. El grupo A todavía no ha completado una expresión await cuando alcanza FinishOneGroupAsync.Group A hasn’t yet completed an await expression when it reaches FinishOneGroupAsync. Por lo tanto, el control no se ha devuelto a AccessTheWebAsync, y la primera asignación a pendingWork no se ha producido.Therefore, control hasn't returned to AccessTheWebAsync, and the first assignment to pendingWork hasn't occurred.

  • Las dos líneas siguientes siempre aparecen juntas en la salida.The following two lines always appear together in the output. El código no se interrumpa nunca entre el inicio de la operación de un grupo en de StartButton_Click y la asignación de una tarea del grupo a pendingWork.The code is never interrupted between starting a group's operation in StartButton_Click and assigning a task for the group to pendingWork.

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

    Una vez que un grupo introduce StartButton_Click, la operación no completa una expresión await hasta que la operación introduce FinishOneGroupAsync.After a group enters StartButton_Click, the operation doesn't complete an await expression until the operation enters FinishOneGroupAsync. Por lo tanto, ninguna otra operación puede lograr el control durante ese segmento de código.Therefore, no other operation can gain control during that segment of code.

Revisión y ejecución de la aplicación de ejemploReviewing and Running the Example App

Para entender mejor la aplicación de ejemplo, puede descargarla, compilarla usted mismo o revisar el código al final de este tema sin necesidad de implementar la aplicación.To better understand the example app, you can download it, build it yourself, or review the code at the end of this topic without implementing the app.

Nota

Para ejecutar el ejemplo como aplicación de escritorio de Windows Presentation Foundation (WPF), debe tener instalado en el equipo Visual Studio 2012 o posterior, y .NET Framework 4.5 o posterior.To run the example as a Windows Presentation Foundation (WPF) desktop app, you must have Visual Studio 2012 or newer and the .NET Framework 4.5 or newer installed on your computer.

Descargar la aplicaciónDownloading the App

  1. Descargue el archivo comprimido de Async Samples: Reentrancy in .NET Desktop Apps (Ejemplos asincrónicos: reentrada en aplicaciones de escritorio de .NET).Download the compressed file from Async Samples: Reentrancy in .NET Desktop Apps.

  2. Descomprima el archivo descargado y, a continuación, inicie Visual Studio.Decompress the file that you downloaded, and then start Visual Studio.

  3. En la barra de menús, elija Archivo, Abrir, Proyecto o solución.On the menu bar, choose File, Open, Project/Solution.

  4. Navegue hasta la carpeta que contiene el código de ejemplo descomprimido y, a continuación, abra el archivo de solución (.sln).Navigate to the folder that holds the decompressed sample code, and then open the solution (.sln) file.

  5. En el Explorador de soluciones, abra el menú contextual del proyecto que quiere ejecutar y, después, elija Establecer como proyecto de inicio.In Solution Explorer, open the shortcut menu for the project that you want to run, and then choose Set as StartUpProject.

  6. Elija las teclas CTRL+F5 para compilar y ejecutar el proyecto.Choose the CTRL+F5 keys to build and run the project.

Compilar la aplicaciónBuilding the App

La sección siguiente proporciona el código para compilar el ejemplo como una aplicación de WPF.The following section provides the code to build the example as a WPF app.

Para compilar una aplicación WPFTo build a WPF app
  1. Inicie Visual Studio.Start Visual Studio.

  2. En la barra de menús, elija Archivo, Nuevo, Proyecto.On the menu bar, choose File, New, Project.

    Aparece el cuadro de diálogo Nuevo proyecto .The New Project dialog box opens.

  3. En el panel Plantillas instaladas, expanda Visual C# y después Windows.In the Installed Templates pane, expand Visual C#, and then expand Windows.

  4. En la lista de tipos de proyecto, seleccione Aplicación WPF.In the list of project types, choose WPF Application.

  5. Asigne el nombre WebsiteDownloadWPF al proyecto y después haga clic en el botón Aceptar.Name the project WebsiteDownloadWPF, and then choose the OK button.

    El proyecto nuevo aparece en el Explorador de soluciones.The new project appears in Solution Explorer.

  6. En el Editor de código de Visual Studio, elija la pestaña MainWindow.xaml .In the Visual Studio Code Editor, choose the MainWindow.xaml tab.

    Si la pestaña no está visible, abra el menú contextual de MainWindow.xaml en el Explorador de soluciones y elija Ver código.If the tab isn’t visible, open the shortcut menu for MainWindow.xaml in Solution Explorer, and then choose View Code.

  7. En la vista XAML de MainWindow.xaml, reemplace el código por el código siguiente.In the XAML view of MainWindow.xaml, replace the code with the following code.

    <Window x:Class="WebsiteDownloadWPF.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="using:WebsiteDownloadWPF"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://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>
    

    En la vista Diseño de MainWindow.xaml aparece una ventana simple que contiene un cuadro de texto y un botón.A simple window that contains a text box and a button appears in the Design view of MainWindow.xaml.

  8. Agregue una referencia para System.Net.Http.Add a reference for System.Net.Http.

  9. En el Explorador de soluciones, abra el menú contextual de MainWindow.xaml.cs y después elija Ver código.In Solution Explorer, open the shortcut menu for MainWindow.xaml.cs, and then choose View Code.

  10. Reemplace el código del archivo MainWindow.xaml.cs por el código siguiente.In MainWindow.xaml.cs, replace the code with the following code.

    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 +=
                    $"\r\n\r\nTOTAL bytes returned:  {total}\r\n";
            }
    
            private List<string> SetUpURLList()
            {
                List<string> urls = new List<string>
                {
                    "https://msdn.microsoft.com/library/hh191443.aspx",
                    "https://msdn.microsoft.com/library/aa578028.aspx",
                    "https://msdn.microsoft.com/library/jj155761.aspx",
                    "https://msdn.microsoft.com/library/hh290140.aspx",
                    "https://msdn.microsoft.com/library/hh524395.aspx",
                    "https://msdn.microsoft.com/library/ms404677.aspx",
                    "https://msdn.microsoft.com",
                    "https://msdn.microsoft.com/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 "https://".
                var displayURL = url.Replace("https://", "");
                // Display position in the URL list, the URL, and the number of bytes.
                ResultsTextBox.Text += $"\n{pos}. {displayURL,-58} {content.Length,8}";
            }
        }
    }
    
  11. Presiones las teclas CTRL+F5 para ejecutar el programa y luego haga clic varias veces en el botón Start.Choose the CTRL+F5 keys to run the program, and then choose the Start button several times.

  12. Realice los cambios de Deshabilitar el botón de inicio, Cancelar y reiniciar la operación o Ejecutar varias operaciones y poner en cola el resultado para controlar la reentrada.Make the changes from Disable the Start Button, Cancel and Restart the Operation, or Run Multiple Operations and Queue the Output to handle the reentrancy.

Vea tambiénSee also