Обработка повторного входа в асинхронных приложениях (C# и Visual Basic)
При включении асинхронного кода в приложение необходимо учесть и, возможно, исключить повторный вход (повторный вход в асинхронную операцию до ее завершения). Если не выявить и не обработать возможности для повторного входа, это может привести к непредвиденным результатам.
Содержание раздела
Примечание
В инструкции Проверка и выполнение приложения примера показано, как выполнять код как приложение Windows Presentation Foundation (WPF) или как приложение Магазина Windows.
Для запуска примеров как WPF необходимо, чтобы на компьютере была установлена Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 для Windows Desktop, Visual Studio Express 2013 для Windows или .NET Framework 4.5 или 4.5.1.
Для выполнения примера как приложения Магазин Windows на компьютере должен быть установлен Windows 8.Кроме того, если требуется запустить пример из Visual Studio, следует установить Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 для Windows Desktop или Visual Studio Express 2013 для Windows.
Распознавание поддержки повторного входа
В примере в этом разделе пользователи нажимают кнопку Запуск, чтобы инициировать асинхронное приложение, которое загружает ряд веб-сайтов и вычисляет общее количество загруженных байтов. Синхронная версия примера реагировала бы одинаковым образом вне зависимости от того, сколько раз пользователь нажимает кнопку, поскольку после первого раза поток пользовательского интерфейса будет игнорировать эти события до завершения выполнения приложения. В асинхронном приложении, однако, поток пользовательского интерфейса продолжает реагировать, и можно повторно войти в асинхронную операцию до ее завершения.
В следующем примере показан ожидаемый выводимый результат, если пользователь выбрал кнопку Пуск только один раз. Отображается список загруженных веб-сайтов с размером в байтах каждого сайта. Общее количество байтов, считанных в буфер.
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
Однако если пользователь нажимает кнопку несколько раз, обработчик событий вызывается повторно, и процесс загрузки каждый раз начинается заново. В результате несколько асинхронных операций выполняется одновременно, в выходных данных результаты чередуются, и общее количество байтов понять сложно.
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
Можно просмотреть код, который дает этот результат, перейдя в конец этого раздела. Можно экспериментировать с кодом, загрузив решение на локальный компьютер и выполняя проект WebsiteDownload либо создав собственный проект с помощью кода в конце этого раздела. Дополнительные сведения и инструкции см. в разделе Анализ и выполнение примера приложения.
Обработка поддержки повторного входа
С реентерабельностью можно работать различными способами, в зависимости от того, что должно делать приложение. Раздел содержит следующие примеры:
-
Отключение кнопки Запуск на время выполнения операции, чтобы пользователь не мог ее прервать.
-
Отмена любой операции, которая все еще выполняется, когда пользователь снова нажимает кнопку Запуск, после чего последняя запрошенная операция может продолжиться.
Запуск нескольких операций и постановка выходных данных в очередь
Разрешите всем необходимым операциям выполняться асинхронно, но скоординируйте отображение выходных данных таким образом, чтобы результаты каждой операции отображались вместе и по порядку.
Отключение кнопки запуска
Можно заблокировать кнопку Запуск на время выполнения операции, отключив эту кнопку в начале обработчика события StartButton_Click. Затем можно снова включить ее из блока finally при завершении операции, чтобы пользователи могли снова запустить приложение.
Следующий код показывает эти изменения, которые отмечены звездочками. Можно добавить изменения в код в конце этого раздела или загрузить готовое приложение со страницы Async Samples: Reentrancy in .NET Desktop Apps или Async Samples: Reentrancy in Windows Store Apps. Имя проекта: 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;
}
}
В результате изменений кнопка не реагирует, пока AccessTheWebAsync загружает веб-сайты, поэтому повторно войти в процесс невозможно.
Отмена и перезапуск операции
Вместо отключения кнопки Запуск можно оставить кнопку активной, но, если пользователь нажмет эту кнопку еще раз, отменить уже выполняющуюся операцию и дать последней запущенной операции возможность продолжить выполнение.
Дополнительные сведения об отмене см. в разделе Настройка асинхронного приложения.
Чтобы настроить этот сценарий, внесите следующие изменения в основной код, который приведен в разделе Проверка и выполнение примера приложения. Можно также загрузить готовое приложение со страницы Async Samples: Reentrancy in .NET Desktop Apps или Async Samples: Reentrancy in Windows Store Apps. Имя данного проекта — CancelAndRestart.
Объявите переменную CancellationTokenSource, cts, которая находится в области видимости для всех методов.
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;
В StartButton_Click определите, выполняется ли уже операция. Если значение cts равно NULL (Nothing в Visual Basic), активной операции еще нет. Если значение не равно NULL, то операция, которая уже выполняется, отменяется.
' *** 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(); }
Задайте свойству cts другое значение, которое представляет текущий процесс.
' *** 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;
В конце StartButton_Click, текущий процесс завершен, поэтому задайте значение cts снова равным 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;
Приведенный ниже код показывает все изменения в StartButton_Click. Добавления помечены звездочками.
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;
}
Внесите следующие изменения в AccessTheWebAsync.
Добавьте параметр для принятия токена отмены от StartButton_Click.
Используйте метод GetAsync для загрузки веб-сайтов, поскольку функция GetAsync принимает аргумент CancellationToken.
Прежде чем вызывать метод DisplayResults для отображения результатов для каждого загруженного веб-сайта, проверьте ct, чтобы убедиться, что текущая операция не была отменена.
Следующий код показывает эти изменения, которые отмечены звездочками.
' *** 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);
}
Если нажать кнопку Запуск несколько раз, пока приложение выполняется, оно должно создать выходные данные, аналогичные показанным ниже.
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
Для исключения частичных списков снимите комментарий в первой строке кода в StartButton_Click, чтобы очищать текстовое поле каждый раз, когда пользователь перезапускает операцию.
Запуск нескольких операций и постановка выходных данных в очередь
Третий пример является самым сложным в том отношении, что приложение запускает новую асинхронную операцию каждый раз, когда пользователь выбирает кнопку Запуск, и все операции выполняются до завершения. Все запрошенные операции загружают веб-сайты из списка асинхронно, но результат выполнения операций представляется последовательно. Это значит, что фактическое действие загрузки чередуется, как видно в Опознание повторного входа, но отображает список результатов для каждой группы отдельно.
Операции используют глобальное Task, pendingWork, который служит для отображения привратник процесса.
Можно запустить этот пример, вставив изменения в код, приведенный в разделе Сборка приложения, или следовать инструкциям в разделе Загрузка приложения, чтобы загрузить пример, и затем запустить проект QueueResults.
В следующем примере показан ожидаемый выводимый результат, если пользователь выбрал кнопку Пуск только один раз. Буквенная метка, А, означает, что результат поступает в момент первого нажатия кнопки Пуск. Числа указывают на порядок URL-адресов в списке целевых объектов загрузки.
#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.
Если пользователь нажимает кнопку Запуск три раза, приложение создает выходные данные, аналогичные следующим строкам. Строки сведений, которые начинаются со знака "#", отслеживают ход выполнения приложения.
#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.
Группы B и С начинаются до завершения группы A, однако выходные данные для каждой группы отображаются отдельно. Сначала отображаются все выходные данные для группы А, затем все выходные данные для группы B, а затем все выходные данные для группы C. Приложение всегда отображаются группы в порядке, для каждой группы, всегда отображаются сведения об отдельных веб-сайтах URL-адреса в том порядке, в котором отображаются в списке URL-адреса.
Однако нельзя предугадать порядок, в котором будут фактически происходить загрузки. После запуска нескольких групп задачи загрузки, которые они создают, все являются активными. Предположить загрузку A-1 до загрузки B-1 невозможно; невозможно также предположить и загрузку A-1 до загрузки A-2.
Глобальные определения
Пример кода содержит следующие 2 глобальных объявлений, отображается из всех методов.
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);
Переменная Task, pendingWork контролирует процесс отображения и предотвращает прерывание одной группой процесса отображения другой. Переменная символа, group метки выходные данные из различных групп, чтобы убедиться, что результаты отображаются в ожидаемый порядок.
Обработчик событий нажатия
Обработчик события StartButton_Click увеличивает букву группы всякий раз, когда пользователь нажимает кнопку Пуск. Затем обработчик вызывает метод AccessTheWebAsync для выполнения операции загрузки.
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.";
}
}
Метод AccessTheWebAsync
Этот пример разделяет AccessTheWebAsync в 2 метода. Первый метод, AccessTheWebAsync, запускает все задачи загрузки для группы и устанавливает pendingWork в элемент управления процессом отображения. Метод использует запрос LINQ (LINQ) и ToArray``1 для запуска всех задач загрузки одновременно.
Затем метод AccessTheWebAsync вызывает метод FinishOneGroupAsync, чтобы ожидать завершения каждой загрузки и отображать ее продолжительность.
FinishOneGroupAsync возвращает задачу, которая назначена pendingWork в AccessTheWebAsync. Это значение предотвращает прерывание другой операции до завершения задачи.
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;
}
Метод FinishOneGroupAsync
Этот метод выполняет циклическую обработку задач загрузки в группе, ожидая каждой и отображая длину загруженного веб-сайта, а также добавляя длину в общее.
Первая выписка в FinishOneGroupAsync использует pendingWork, чтобы вставить метод, который не мешает операции, которая уже в процессе отображения или ожидания. Если такая операция выполняется, входящая операция должна ожидать своей очереди.
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);
}
Можно запустить этот пример, вставив изменения в код, приведенный в разделе Сборка приложения, или следовать инструкциям в разделе Загрузка приложения, чтобы загрузить пример, и затем запустить проект QueueResults.
Интересующие точки
Линии сведений, начинающиеся с символа "#" в выходе показывают, как работает этот пример.
Результат содержит следующие шаблоны.
Группу можно запустить, пока предыдущая группа отображает свои выходные данные, причем вывод данных предыдущей группы прерван не будет.
#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
Задача pendingWork значение NULL (Nothing в Visual Basic) в начале FinishOneGroupAsync только для группы a, которая запустила первым. На момент достижения FinishOneGroupAsync группа А еще не завершила выражение ожидания. Поэтому элемент управления не вернулся в AccessTheWebAsync, а первое присвоение pendingWork не произошло.
Следующие 2 линии всегда отображаются вместе в выходные данные. Код никогда не прерван между начать операцию групп в StartButton_Click и присвоив задачи для группы в pendingWork.
#Starting group B. #Task assigned for group B. Download tasks are active.
После того, как группа входит в StartButton_Click, операция не завершает выражение ожидания, пока операция не войдет в FinishOneGroupAsync. Поэтому никакая другая операция не может управлять коэффициентом усиления во время этого сегмента кода.
Проверка и выполнение примера приложения
Чтобы лучше понять пример приложения, можно загрузить его, самостоятельно выполнить его сборку или проверить код в конце этого раздела без реализации приложения.
Примечание
Для запуска примера как классического приложения Windows Presentation Foundation (WPF) необходимо, чтобы на компьютере была установлена Visual Studio 2012, Visual Studio 2013, Visual Studio Express 2012 для Windows Desktop, Visual Studio Express 2013 для Windows или .NET Framework 4.5 или 4.5.1.
Для выполнения примера как приложения Магазин Windows на компьютере должен быть установлен Windows 8.Кроме того, если требуется запустить пример из Visual Studio, следует установить Visual Studio 2012, Visual Studio 2013 Visual Studio Express 2012 для Windows 8, Visual Studio Express 2013 для Windows.Visual Studio 2010 не может загружать проекты, предназначенные для .NET Framework 4.5.
Загрузка приложения
Загрузите сжатый файл со страницы Async Samples: Reentrancy in .NET Desktop Apps или Async Samples: Reentrancy in Windows Store Apps.
Распакуйте загруженный файл и запустите Visual Studio.
В строке меню выберите Файл, Открыть, Проект/Решение.
Перейдите к папке, содержащей распакованный образец кода, откройте файл решения (sln).
В Обозревателе решений откройте контекстное меню для проекта, который требуется запустить, и выберите Назначить запускаемым проектом.
Нажмите клавиши CTRL+F5, чтобы собрать и запустить проект.
Построение приложения
Следующие разделы содержат код для сборки примера как приложение WPF или как приложение Магазин Windows.
Построение приложения WPF
Запустите Visual Studio.
В меню Файл выберите Создать, Проект.
Откроется диалоговое окно Новый проект.
В области Установленные шаблоны разверните узел Visual Basic или Visual C# и разверните Windows.
В списке типов проектов выберите Приложение WPF.
Назовите проект WebsiteDownloadWPF и нажмите кнопку ОК.
В обозревателе решений появится новый проект.
Выберите в редакторе кода Visual Studio вкладку MainWindow.xaml.
Если вкладка не отображается, откройте контекстное меню для MainWindow.xaml в обозревателе решений, а затем выберите Просмотреть код.
В представлении XAML MainWindow.xaml замените код на следующий код.
<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>
Простое окно, содержащее текстовое поле и кнопку, отображается в представлении Конструктор MainWindow.xaml.
Добавьте ссылку на System.Net.Http.
В Обозревателе решений откройте контекстное меню элемента MainWindow.xaml.vb или MainWindow.xaml.cs и выберите команду Просмотреть код.
В файле MainWindow.xaml.vb или MainWindow.xaml.cs замените код на следующий.
' 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); } } }
Нажмите клавиши CTRL+F5, чтобы запустить программу, затем нажмите кнопку Start несколько раз.
Внесите изменения в Отключение кнопки "Пуск", Отмена и перезапуск операции или Выполнение нескольких операций и постановка вывода в очередь для обработки повторного входа
Построение приложения для Магазина Windows
Запустите Visual Studio.
В меню Файл выберите Создать, Проект.
Откроется диалоговое окно Новый проект.
В категории Установленные, Шаблоны выберите Visual Basic или Visual C# и разверните Магазин Windows.
В списке типов проектов выберите Пустое приложение (XAML).
Назовите проект WebsiteDownloadWin и нажмите кнопку ОК.
В обозревателе решений появится новый проект.
В Обозревателе решений откройте контекстное меню для MainPage.xaml и выберите Открыть.
В окне XAML MainPage.xaml замените автоматически созданный код на следующий код.
<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>
Простое окно, содержащее текстовое поле и кнопку Пуск, отображается в окне Конструктор MainPage.xaml.
В Обозревателе решений откройте контекстное меню для MainPage.xaml.vb или MainPage.xaml.cs и выберите Просмотреть код.
В файле MainPage.xaml.vb или MainPage.xaml.cs замените код на следующий.
' 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); } } }
Нажмите клавиши CTRL+F5, чтобы запустить программу, затем нажмите кнопку Start несколько раз.
Внесите изменения в Отключение кнопки "Пуск", Отмена и перезапуск операции или Выполнение нескольких операций и постановка вывода в очередь для обработки повторного входа
См. также
Задачи
Основные понятия
Асинхронное программирование с использованием ключевых слов Async и Await (C# и Visual Basic)
Другие ресурсы
Асинхронное программирование (приложений Магазина Windows)
Учебник. Вызов асинхронных интерфейсов API в C# или Visual Basic