Gestion de la réentrance dans Async Apps (C# et Visual Basic)
Lorsque vous incluez un code asynchrone dans votre application, vous devez considérer et éventuellement empêcher la réentrance, qui fait référence au fait d'ouvrir à nouveau une opération asynchrone avant qu'elle ne soit achevée. Si vous n'identifiez pas ni ne gérez les possibilités de réentrance, cela peut provoquer des résultats inattendus.
Dans cette rubrique
Notes
Les instructions dans Examen et exécution de l'exemple d'application expliquent comment exécuter du code en tant qu'application Windows Presentation Foundation (WPF) ou en tant qu'application du Windows Store.
Pour exécuter l'exemple en tant qu'application WPF, Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 pour Windows Desktop, Visual Studio Express 2013 pour Windows ou le .NET Framework 4.5 ou 4.5.1 doit être installé sur votre ordinateur.
Pour exécuter l'exemple en tant qu'application Windows Store, vous devez installer Windows 8 sur votre ordinateur.En outre, si vous souhaitez exécuter l'exemple dans Visual Studio, vous devez avoir aussi installé Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 pour Windows Desktop ou Visual Studio Express 2013 pour Windows.
Identification de la réentrance
Dans l'exemple décrit dans cette rubrique, les utilisateurs cliquent sur un bouton Démarrer pour initialiser une application asynchrone qui télécharge une série de sites Web et calcule le nombre total d'octets téléchargés. Une version synchrone de l'exemple réagirait de la même façon indépendamment du nombre de fois qu'un utilisateur choisit le bouton car, après la première fois, le thread d'interface utilisateur ignore ces événements jusqu'à la fin de l'exécution de l'application. Toutefois, dans une application asynchrone, le thread d'interface utilisateur continue de répondre, et vous pouvez réactiver l'opération asynchrone avant qu'elle soit terminée.
L'exemple suivant illustre la sortie attendue si l'utilisateur clique une fois sur le bouton Démarrer. Une liste des sites Web téléchargés apparaît avec la taille, en octets, de chaque site. Le nombre total d'octets apparaît à la fin.
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
Toutefois, si l'utilisateur clique plusieurs fois sur le bouton, le gestionnaire d'événements est appelé à plusieurs reprises, et le processus de téléchargement est réactivé à chaque fois. Par conséquent, plusieurs opérations asynchrones sont exécutées simultanément, les résultats sont entrelacés, et le nombre total d'octets est ambigu.
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
Vous pouvez examiner le code qui génère cette sortie en faisant défiler l'écran jusqu'à la fin de cette rubrique. Vous pouvez tester le code en téléchargeant la solution sur votre ordinateur local, puis en exécutant le projet WebsiteDownload ou en utilisant le code à la fin de cette rubrique pour créer votre propre projet. Pour plus d'informations et d'instructions, consultez Examen et exécution de l'exemple d'application.
Gestion de la réentrance
Vous pouvez gérer la réentrance de diverses façons, selon ce que votre application doit faire. Cette rubrique présente les exemples suivants :
-
Désactivez le bouton Démarrer pendant que l'opération s'exécute afin que l'utilisateur ne puisse pas l'interrompre.
Annuler et redémarrer l'opération
Supprimez toute opération encore en cours d'exécution lorsque l'utilisateur choisit le bouton Démarrer, puis laissez se poursuivre l'opération la plus récemment demandée.
Exécuter plusieurs opérations et mettre la sortie en file d'attente
Permet à toutes les opérations demandées de s'exécuter de façon asynchrone, mais coordonne l'affichage de la sortie de sorte que les résultats de chaque opération apparaissent ensemble et dans l'ordre.
Désactiver le bouton Démarrer
Vous pouvez bloquer le bouton Démarrer lorsqu'une opération est en cours d'exécution en désactivant le bouton en haut du gestionnaire d'événements StartButton_Click. Vous pouvez alors réactiver le bouton à partir d'un bloc finally lorsque l'opération se termine, afin que les utilisateurs puissent exécuter l'application.
Le code suivant illustre ces modifications, qui sont marquées avec des astérisques. Vous pouvez ajouter des modifications au code à la fin de cette rubrique, ou vous pouvez télécharger l'application finie depuis Exemples asynchrones : réentrance dans les applications de bureau .NET ou Exemples asynchrones : réentrance dans les applications Windows Store. Le nom de projet est 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;
}
}
En conséquence des modifications, le bouton ne répond pas pendant que AccessTheWebAsync télécharge les sites Web, et le processus ne peut pas être réentré.
Annuler et redémarrer l'opération
Au lieu de désactiver le bouton Démarrer, vous pouvez le maintenir actif mais, si l'utilisateur clique à nouveau dessus, annulez l'opération qui est déjà en cours et laissez la dernière opération démarrée se poursuivre.
Pour plus d'informations sur l'annulation, consultez Régler votre application Async.
Pour installer ce scénario, apportez les modifications suivantes au code de base fourni dans Examen et exécution de l'exemple d'application. Vous pouvez également télécharger l'application finie à partir d'Exemples asynchrones : réentrance dans les applications de bureau .NET ou Exemples asynchrones : réentrance dans les applications Windows Store. Le nom de ce projet est CancelAndRestart.
Déclarez une variable CancellationTokenSource, cts, qui est dans la portée de toutes les méthodes.
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;
Dans StartButton_Click, déterminez si une opération est déjà en cours. Si cts a la valeur null (Nothing en Visual Basic), aucune opération n'est déjà active. Si la valeur n'est pas null, l'opération en cours est annulée.
' *** 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(); }
Affectez à cts une valeur différente qui représente le processus actuel.
' *** 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;
À la fin de StartButton_Click, le processus actuel est terminé. Vous devez donc redéfinir la valeur de 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;
Le code suivant illustre tous les changements dans StartButton_Click. Les ajouts sont marqués avec des astérisques.
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;
}
Dans AccessTheWebAsync, apportez les modifications suivantes.
Ajoutez un paramètre pour accepter le jeton d'annulation de StartButton_Click.
Utilisez la méthode GetAsync pour télécharger les sites Web car GetAsync accepte un argument CancellationToken.
Avant d'appeler DisplayResults pour afficher les résultats de chaque site Web téléchargé, vérifiez ct pour vérifier que l'opération en cours n'a pas été annulée.
Le code suivant illustre ces modifications, qui sont marquées avec des astérisques.
' *** 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 vous cliquez plusieurs fois sur le bouton Démarrer pendant l'exécution de cette application, cela doit produire des résultats qui ressemblent à la sortie suivante.
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
Pour empêcher des listes partielles, supprimez des commentaires de la première ligne de code dans StartButton_Click pour effacer la zone de texte chaque fois que l'utilisateur redémarre l'opération.
Exécuter plusieurs opérations et mettre la sortie en file d'attente
Ce troisième exemple est plus complexe car l'application démarre une autre opération asynchrone chaque fois que l'utilisateur choisit le bouton Démarrer, et toutes les opérations exécutées à l'achèvement. Toutes les opérations demandées téléchargent des sites Web de la liste de façon asynchrone, mais la sortie des opérations est présentée en séquence. Autrement dit, l'activité de téléchargement réelle est entrelacée, comme la sortie dans Identification de la réentrance le montre, mais la liste des résultats pour chaque groupe est présentée séparément.
Les opérations partagent un Taskglobal, pendingWork, qui sert d'opérateur de contrôle d'appels au processus d'affichage.
Vous pouvez exécuter cet exemple en insérant les modifications dans le code de Génération de l'application ou vous pouvez suivre les instructions de Téléchargement de l'application pour télécharger l'exemple, puis exécuter le projet QueueResults.
La sortie suivante illustre le résultat obtenu si l'utilisateur clique une fois sur le bouton Démarrer. L'étiquette de lettre, A, indique que le résultat provient de la première fois que le bouton Démarrer est sélectionné. Les nombres indiquent l'ordre des URL dans la liste de cibles de téléchargement.
#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 l'utilisateur clique trois fois sur le bouton Démarrer, l'application génère une sortie qui ressemble aux lignes suivantes. Les lignes d'information qui commencent par un signe dièse (#) suive la progression de l'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.
Les groupes B et C ont commencé avant que le groupe A ait terminé, mais la sortie pour chaque groupe apparaît séparément. Toute la sortie du groupe A apparaît en premier, suivie de toute la sortie du groupe B, et de toute la sortie du groupe C. L'application affiche toujours les groupes dans l'ordre et, pour chaque groupe, affiche toujours les informations relatives à chaque site Web dans l'ordre d'affichage des URL dans la liste d'URL.
Toutefois, vous ne pouvez pas prévoir l'ordre réel des téléchargements. Lorsque plusieurs groupes ont été démarrés, les tâches de téléchargement qu'ils génèrent sont tous actives. Vous ne pouvez pas supposer que A-1 sera téléchargé avant B-1, et vous ne pouvez pas supposer que A-1 sera téléchargé avant A-2.
Définitions globales
L'exemple de code contient les deux déclarations globales suivantes qui sont visibles à partir de toutes les méthodes.
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, surveille le processus d'affichage et empêche les groupes d'interrompre le fonctionnement de l'affichage d'un autre groupe. La variable de caractère, group, étiquette la sortie de différents groupes pour vérifier que les résultats apparaissent dans l'ordre prévu.
Gestionnaire d'événements Click
Le gestionnaire d'événements, StartButton_Click, incrémente la lettre du groupe chaque fois que l'utilisateur clique sur le bouton Démarrer. Ensuite le gestionnaire appelle AccessTheWebAsync pour exécuter l'opération de téléchargement.
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.";
}
}
Méthode AccessTheWebAsync
Cet exemple scinde AccessTheWebAsync en deux méthodes. La première méthode, AccessTheWebAsync, démarre toutes les tâches de téléchargement pour un groupe et configure pendingWork pour contrôler le processus d'affichage. La méthode utilise LINQ (Langage-Integrated Query) et ToArray``1 pour démarrer toutes les tâches de téléchargement en même temps.
AccessTheWebAsync appelle ensuite FinishOneGroupAsync pour attendre la fin de chaque téléchargement et afficher sa longueur.
FinishOneGroupAsync retourne une tâche assignée à pendingWork dans AccessTheWebAsync. Cette valeur empêche l'interruption par une autre opération avant la fin de la tâche.
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;
}
Méthode FinishOneGroupAsync
Cette méthode parcourt les tâches de téléchargement d'un groupe, attend chacune d'entre-elle, affiche la longueur du site Web téléchargé, et ajoute la longueur au total.
La première instruction dans FinishOneGroupAsync utilise pendingWork pour vérifier que l'activation de la méthode n'interfère pas avec une opération qui se trouve déjà dans le processus d'affichage ou qui est déjà en attente. Si cette opération est en cours, l'opération entrante doit attendre son tour.
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);
}
Vous pouvez exécuter cet exemple en insérant les modifications dans le code de Génération de l'application ou vous pouvez suivre les instructions de Téléchargement de l'application pour télécharger l'exemple, puis exécuter le projet QueueResults.
Points d'intérêt
Les lignes d'information qui commencent par un signe dièse (#) dans la sortie clarifient comment fonctionne cet exemple.
La sortie affiche les modèles suivants.
Un groupe peut être démarré pendant qu'un groupe précédent affiche sa sortie, mais l'affichage de la sortie du groupe précédent n'est pas interrompu.
#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 tâche pendingWork est null (Nothing dans Visual Basic) au début de FinishOneGroupAsync uniquement pour le groupe A, qui a démarré en premier. Le groupe A n'a pas encore terminé une expression await lorsqu'elle atteint FinishOneGroupAsync. Par conséquent, le contrôle n'est pas retourné à AccessTheWebAsync, et la première assignation à pendingWork ne s'est pas produite.
Les deux lignes suivantes apparaissent toujours définies dans la sortie. Le code n'est jamais interrompu entre le démarrage d'une opération de groupe dans StartButton_Click et l'assignation d'une tâche du groupe à pendingWork.
#Starting group B. #Task assigned for group B. Download tasks are active.
Lorsqu'un groupe entre StartButton_Click, l'opération n'effectue pas d'expression await avant que l'opération entre FinishOneGroupAsync. Par conséquent, aucune autre opération ne peut prendre le contrôle pendant ce segment de code.
Examen et exécution de l'exemple d'application
Pour mieux comprendre l'exemple d'application, vous pouvez la télécharger, la générer vous-même, ou examiner le code à la fin de cette rubrique sans implémenter l'application.
Notes
Pour exécuter l'exemple en tant qu'application de bureau Windows Presentation Foundation (WPF), Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 pour Windows Desktop Visual Studio Express 2013 pour Windows ou le .NET Framework 4.5 ou 4.5.1 doit être installé sur votre ordinateur.
Pour exécuter l'exemple en tant qu'application Windows Store, vous devez installer Windows 8 sur votre ordinateur.En outre, si vous souhaitez exécuter l'exemple dans Visual Studio, vous devez avoir aussi installé Visual Studio 2012, Visual Studio 2013Visual Studio Express 2012 pour Windows 8 ou Visual Studio Express 2013 pour Windows.Visual Studio 2010 ne peut pas charger les projets qui sont ciblés pour .NET Framework 4.5.
Téléchargement de l'application
Téléchargez le fichier compressé depuis Exemples asynchrones : réentrance dans les applications de bureau .NET ou Exemples asynchrones : réentrance dans les applications Windows Store.
Décompressez le fichier que vous avez téléchargé, puis démarrez Visual Studio.
Dans la barre de menus, sélectionnez Fichier, Ouvrir, Projet/Solution.
Accédez au dossier qui contient l'exemple de code décompressé, puis ouvrez le fichier solution (.sln).
Dans l'Explorateur de solutions, ouvrez le menu contextuel du projet que vous souhaitez exécuter et choisissez Définir comme projet de démarrage.
Appuyez sur les touches CTRL+F5 pour générer et exécuter le projet.
Génération de l'application
Les sections suivantes fournissent le code pour générer l'exemple comme une application WPF ou comme une application Windows Store.
Pour générer une application WPF
Démarrez Visual Studio.
Dans la barre de menus, sélectionnez Fichier, Nouveau, Projet.
La boîte de dialogue Nouveau projet s'affiche.
Dans le volet Modèles installés, développez Visual Basic ou Visual C#, puis développez Windows.
Dans la liste des types de projet, choisissez Application WPF.
Nommez le projet WebsiteDownloadWPF, puis choisissez OK.
Le nouveau projet s'affiche dans l'Explorateur de solutions.
Dans l'éditeur de code Visual Studio, choisissez l'onglet MainWindow.xaml.
Si l'onglet n'est pas visible, ouvrez le menu contextuel de MainWindow.xaml dans l'Explorateur de solutions, puis choisissez Afficher le code.
Dans la vue XAML de MainWindow.xaml, remplacez le code par le code suivant.
<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>
Une fenêtre simple qui contient une zone de texte et un bouton apparaît dans la vue Design de MainWindow.xaml.
Ajouter la référence System.Net.Http.
Dans l'Explorateur de solutions, ouvrez le menu contextuel de MainWindow.xaml.vb ou MainWindow.xaml.cs, puis choisissez Afficher le Code.
Dans MainWindow.xaml.vb ou MainWindow.xaml.cs, remplacez le code par le code suivant.
' 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); } } }
Choisissez les touches CTRL+F5 pour exécuter le programme, puis choisissez le bouton Démarrer plusieurs fois.
Apportez les modifications à partir de Désactiver le bouton Démarrer, Annuler et redémarrer l'opération ou Exécuter plusieurs opérations et mettre la sortie en file d'attente pour gérer la réentrance.
Pour créer une application Windows Store
Démarrez Visual Studio.
Dans la barre de menus, sélectionnez Fichier, Nouveau, Projet.
La boîte de dialogue Nouveau projet s'affiche.
Dans la catégorie Installé, Modèles, développez Visual Basic ou Visual C#, puis Windows Store.
Dans la liste des types de projet, choisissez Application vide (XAML).
Nommez le projet WebsiteDownloadWin, puis choisissez OK.
Le nouveau projet s'affiche dans l'Explorateur de solutions.
Dans l'Explorateur de solutions, ouvrez le menu contextuel de MainPage.xaml, puis choisissez Ouvrir.
Dans la fenêtre XAML de MainPage.xaml, remplacez le code par le code suivant.
<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>
Une fenêtre simple qui contient une zone de texte et un bouton de démarrage apparaît dans la fenêtre Design de MainPage.xaml.
Dans l'Explorateur de solutions, ouvrez le menu contextuel de MainPage.xaml.vb ou MainPage.xaml.cs, puis choisissez Afficher le Code.
Remplacez le code dans MainPage.xaml.vb ou MainPage.xaml.cs par le code suivant.
' 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); } } }
Choisissez les touches CTRL+F5 pour exécuter le programme, puis choisissez le bouton Démarrer plusieurs fois.
Apportez les modifications à partir de Désactiver le bouton Démarrer, Annuler et redémarrer l'opération ou Exécuter plusieurs opérations et mettre la sortie en file d'attente pour gérer la réentrance.
Voir aussi
Tâches
Procédure pas à pas : accès au Web avec Async et Await (C# et Visual Basic)
Concepts
Programmation asynchrone avec Async et Await (C# et Visual Basic)
Autres ressources
Programmation asynchrone (applications Windows Store)
Démarrage rapide : appel d'API asynchrones en C# ou Visual Basic