处理异步应用程序中的重入(C# 和 Visual Basic)

在应用程序中添加异步代码时,应考虑并尽可能避免重新进入,重新进入指在完成异步操作前重新进入异步操作。 如果未识别并处理重新进入的可能性,将可能导致意外的结果。

主题内容

备注

检查并运行此示例应用程序中的说明向您展示如何将代码作为 Windows Presentation Foundation (WPF) 应用程序或作为 Windows 应用商店应用进行运行。

要运行 WPF 应用程序示例,您必须在您的计算机上安装 Visual Studio 2012、Visual Studio 2013、Visual Studio Express 2012 for Windows Desktop、Visual Studio Express 2013 for Windows 或 .NET Framework 4.5 或 4.5.1。

若要运行 Windows 应用商店 应用程序示例,您必须在计算机上安装 Windows 8。此外,如要从 Visual Studio 运行示例,还必须安装 Visual Studio 2012、Visual Studio 2013、Visual Studio Express 2012 for Windows Desktop 或 Visual Studio Express 2013 for Windows。

识别重新进入

在本主题中的示例中,用户选择“开始”按钮启动一个异步应用程序来下载一系列网站并计算下载的总字节数。 无论用户选择该按钮多少次,该示例的同步版本将以同一方式响应,这是因为,在首次后,直到该应用程序完成运行,UI 线程都忽略这些事件。 在异步应用程序中,然而,UI 线程继续执行响应,并可能需在其完成前重新输入异步操作。

如果用户只选择“启动”按钮一次,以下示例演示预期的输出。 下载网站的列表显示了每个站点的大小(以字节为单位)。 总字节数显示在终端。

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 块内重新启动按钮,这样一来,用户可再次运行应用程序。

以下代码显示这些已标记星号的更改。 您可将更改添加到本主题末尾的代码,或者您可从异步示例:.NET 桌面应用程序中的重新进入异步示例:Windows 应用商店应用中的重新进入下载已完成的应用。 项目名称是 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 下载网站时按钮不会响应,因此无法重新输入过程。

取消和重新启动操作

如果用户再次选择该按钮,您可以保持激活按钮,而不是禁用“启动”按钮,但取消已运行并允许最近开始的操作继续。

有关取消的详细信息,请参阅微调您的异步应用程序

若要设置此方案,请对在检查并运行此示例应用程序中提供的基本代码进行以下更改。 您也可从 .NET 桌面应用程序中的异步示例:Windows 应用商店应用下载已完成的应用程序。 该项目的名称为 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(Visual Basic 中为 Nothing),则未启动任一操作。 如果值不为 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 项目。

如果用户只选择“启动”按钮一次,以下输出演示输出。 字母标签 A 表示该结果来自首次选中“启动”按钮。 数字显示下载目标列表的 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 组和 C 组在 A 组结束前开始,但是每组的输出将单独出现。 首先显示 A 组的所有输出,再显示 B 组的所有输出,然后显示 C 组的所有输出。 该应用程序始终显示有序的组,并且每个组始终按照 URL 列表中的 URL 顺序显示单个网站的信息。

但是,您无法预测实际的下载顺序。 在多个组启动后,它们生成的下载任务都是活动的。 您不能假定 A-1 将先于 B-1 下载,且您不能假定 A-1 将先于 A-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 拆分为两种方法。 第一种方法(AccessTheWebAsync)启动组的所有下载任务并将 pendingWork 设置为控制显示进程。 方法同时使用语言集成查询(LINQ 查询)和 ToArray``1 启动下载任务。

AccessTheWebAsync 然后调用 FinishOneGroupAsync 等待每个下载完成并显示其长度。

FinishOneGroupAsync 返回分配给 AccessTheWebAsync 中的 pendingWork 的任务。 该值防止在任务完成前由另一个操作引起的中断。

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
    
  • 仅对于最先启动的 A 组,pendingWork 任务才会在 FinishOneGroupAsync 启动的时候为 null(Nothing 在 Visual Basic 中)。 在到达 FinishOneGroupAsync 时,A 组尚未完成 Await 表达式。 因此,控件尚未返回到 AccessTheWebAsync,并且,对于 pendingWork 的第一个分配也未发生。

  • 以下两行始终在输出中显示在一起。 代码始终不会在启动 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 for Windows Desktop、Visual Studio Express 2013 for Windows 或 .NET Framework 4.5 或 4.5.1。

若要运行 Windows 应用商店 应用程序示例,您必须在计算机上安装 Windows 8。此外,如要从 Visual Studio 运行示例,还必须安装 Visual Studio 2012、Visual Studio 2013、Visual Studio express 2012 for Windows 8 或Visual Studio Express 2013 for Windows。Visual Studio 2010 无法加载面向 .NET Framework 4.5 的项目。

下载应用程序

  1. 异步示例:重新进入 .NET 桌面应用程序异步示例:重新进入 Windows 应用商店应用下载压缩文件。

  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. 在 MainWindow.xaml 的“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 键运行程序,然后多次选择“开始”按钮。

  12. 停用开始按钮取消和重启操作运行多操作和排队输出更改为处理重新进入。

生成 Windows 应用商店应用

  1. 启动 Visual Studio。

  2. 在菜单栏上,选择**“文件”“新建**、“项目”

    将打开**“新建项目”**对话框。

  3. 在“已安装”、“模板”类别中,展开“Visual Basic”或“Visual C#”,然后展开“Windows 应用商店”。

  4. 在项目类型列表中,选择“空白应用程序 (XAML)”。

  5. 将项目命名为 WebsiteDownloadWin,然后选择“确定”按钮。

    新项目出现在**“解决方案资源管理器”**中。

  6. 在“解决方案资源管理器”中,打开 MainPage.xaml 的快捷菜单,然后选择“打开”。

  7. 在 MainPage.xaml 的“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 键运行程序,然后多次选择“开始”按钮。

  11. 停用开始按钮取消和重启操作运行多操作和排队输出更改为处理重新进入。

请参见

任务

演练:使用 Async 和 Await 访问 Web(C# 和 Visual Basic)

概念

使用 Async 和 Await 的异步编程(C# 和 Visual Basic)

其他资源

异步编程(Windows 应用商店应用)

Quickstart: Calling asynchronous APIs in C# or Visual Basic