Поделиться через


Обработка повторного входа в асинхронных приложениях (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.

  1. Объявите переменную 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;
    
  2. В 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();
    }
    
  3. Задайте свойству 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;
    
  4. В конце 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.

Загрузка приложения

  1. Загрузите сжатый файл со страницы Async Samples: Reentrancy in .NET Desktop Apps или Async Samples: Reentrancy in Windows Store Apps.

  2. Распакуйте загруженный файл и запустите Visual Studio.

  3. В строке меню выберите Файл, Открыть, Проект/Решение.

  4. Перейдите к папке, содержащей распакованный образец кода, откройте файл решения (sln).

  5. В Обозревателе решений откройте контекстное меню для проекта, который требуется запустить, и выберите Назначить запускаемым проектом.

  6. Нажмите клавиши CTRL+F5, чтобы собрать и запустить проект.

Построение приложения

Следующие разделы содержат код для сборки примера как приложение WPF или как приложение Магазин Windows.

Построение приложения WPF

  1. Запустите Visual Studio.

  2. В меню Файл выберите Создать, Проект.

    Откроется диалоговое окно Новый проект.

  3. В области Установленные шаблоны разверните узел Visual Basic или Visual C# и разверните Windows.

  4. В списке типов проектов выберите Приложение WPF.

  5. Назовите проект WebsiteDownloadWPF и нажмите кнопку ОК.

    В обозревателе решений появится новый проект.

  6. Выберите в редакторе кода Visual Studio вкладку MainWindow.xaml.

    Если вкладка не отображается, откройте контекстное меню для MainWindow.xaml в обозревателе решений, а затем выберите Просмотреть код.

  7. В представлении 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.

  8. Добавьте ссылку на System.Net.Http.

  9. В Обозревателе решений откройте контекстное меню элемента MainWindow.xaml.vb или MainWindow.xaml.cs и выберите команду Просмотреть код.

  10. В файле 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);
            }
        }
    }
    
  11. Нажмите клавиши CTRL+F5, чтобы запустить программу, затем нажмите кнопку Start несколько раз.

  12. Внесите изменения в Отключение кнопки "Пуск", Отмена и перезапуск операции или Выполнение нескольких операций и постановка вывода в очередь для обработки повторного входа

Построение приложения для Магазина Windows

  1. Запустите Visual Studio.

  2. В меню Файл выберите Создать, Проект.

    Откроется диалоговое окно Новый проект.

  3. В категории Установленные, Шаблоны выберите Visual Basic или Visual C# и разверните Магазин Windows.

  4. В списке типов проектов выберите Пустое приложение (XAML).

  5. Назовите проект WebsiteDownloadWin и нажмите кнопку ОК.

    В обозревателе решений появится новый проект.

  6. В Обозревателе решений откройте контекстное меню для MainPage.xaml и выберите Открыть.

  7. В окне 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.

  8. В Обозревателе решений откройте контекстное меню для MainPage.xaml.vb или MainPage.xaml.cs и выберите Просмотреть код.

  9. В файле 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);
            }
        }
    }
    
  10. Нажмите клавиши CTRL+F5, чтобы запустить программу, затем нажмите кнопку Start несколько раз.

  11. Внесите изменения в Отключение кнопки "Пуск", Отмена и перезапуск операции или Выполнение нескольких операций и постановка вывода в очередь для обработки повторного входа

См. также

Задачи

Пошаговое руководство. Получение доступа к Интернету с помощью модификатора Async и оператора Await (C# и Visual Basic)

Основные понятия

Асинхронное программирование с использованием ключевых слов Async и Await (C# и Visual Basic)

Другие ресурсы

Асинхронное программирование (приложений Магазина Windows)

Учебник. Вызов асинхронных интерфейсов API в C# или Visual Basic