我如何等待.Net中列表中第一个完成的异步任务?

时间:2012-09-14 18:31:03

标签: .net vb.net async-await

我的输入是位于Amazon S3服务器上的一长串文件。我想下载文件的元数据,计算本地文件的哈希值,并将元数据哈希值与本地文件的哈希值进行比较。

目前,我使用循环以异步方式启动所有元数据下载,然后在每个完成时,根据需要在本地文件上计算MD5并进行比较。这是代码(只是相关的行):

Dim s3client As New AmazonS3Client(KeyId.Text, keySecret.Text)
Dim responseTasks As New List(Of System.Tuple(Of ListViewItem, Task(Of GetObjectMetadataResponse)))
For Each lvi As ListViewItem In lvStatus.Items
    Dim gomr As New Amazon.S3.Model.GetObjectMetadataRequest
    gomr.BucketName = S3FileDialog.GetBucketName(lvi.SubItems(2).Text)
    gomr.Key = S3FileDialog.GetPrefix(lvi.SubItems(2).Text)
    responseTasks.Add(New System.Tuple(Of ListViewItem, Task(Of GetObjectMetadataResponse))(lvi, s3client.GetObjectMetadataAsync(gomr)))
Next
For Each t As System.Tuple(Of ListViewItem, Task(Of GetObjectMetadataResponse)) In responseTasks
    Dim response As GetObjectMetadataResponse = Await t.Item2
    If response.ETag.Trim(""""c) = MD5CalcFile(lvi.SubItems(1).Text) Then
        lvi.SubItems(3).Text = "Match"
        UpdateLvi(lvi)
    End If
Next

我有两个问题:

  1. 我正按照我制作的顺序等待回复。我宁愿按照他们完成的顺序处理它们,以便让它们更快。

  2. MD5计算是长且同步的。我尝试将其设为异步,但进程已锁定。我认为MD5任务被添加到.Net的任务列表的末尾,并且在完成所有下载之前它没有运行。

  3. 理想情况下,我会在响应到达时处理响应,而不是按顺序处理,并且MD5是异步的但有机会运行。

    修改

    合并WhenAll,现在看起来像这样:

    Dim s3client As New Amazon.S3.AmazonS3Client(KeyId.Text, keySecret.Text)
    Dim responseTasks As New Dictionary(Of Task(Of GetObjectMetadataResponse), ListViewItem)
        For Each lvi As ListViewItem In lvStatus.Items
            Dim gomr As New Amazon.S3.Model.GetObjectMetadataRequest
            gomr.BucketName = S3FileDialog.GetBucketName(lvi.SubItems(2).Text)
            gomr.Key = S3FileDialog.GetPrefix(lvi.SubItems(2).Text)
            responseTasks.Add(s3client.GetObjectMetadataAsync(gomr), lvi)
        Next
        Dim startTime As DateTimeOffset = DateTimeOffset.Now
        Do While responseTasks.Count > 0
            Dim currentTask As Task(Of GetObjectMetadataResponse) = Await Task.WhenAny(responseTasks.Keys)
            Dim response As GetObjectMetadataResponse = Await currentTask
            If response.ETag.Trim(""""c) = MD5CalcFile(lvi.SubItems(1).Text) Then
                lvi.SubItems(3).Text = "Match"
                UpdateLvi(lvi)
            End If
        Loop
        MsgBox((DateTimeOffset.Now - startTime).ToString)
    

    只要MDSCalcFile完成,UI就会暂时锁定。整个循环大约需要45秒,第一个文件的MD5结果在启动后的1秒内发生。

    如果我将该行更改为:

            If response.ETag.Trim(""""c) = Await Task.Run(Function () MD5CalcFile(lvi.SubItems(1).Text)) Then
    

    完成MD5CalcFile后,UI无法锁定。整个循环大约需要75秒,从45秒开始,第一个文件的MD5结果在等待40秒后发生。

    EDIT2:

    我找到了一个适合我的解决方案。问题出在我的GetObjectMetadataAsync中。我写错了。评论中错误的正确版本如下:

    <System.Runtime.CompilerServices.Extension>
    Function GetObjectMetadataAsync(a As AmazonS3Client, l As GetObjectMetadataRequest) As Task(Of GetObjectMetadataResponse)
        Return Task.Factory.FromAsync(AddressOf a.BeginGetObjectMetadata, AddressOf a.EndGetObjectMetadata, l, Nothing)
        'Return Task.Run(Function()
        '                    Try
        '                        Return a.GetObjectMetadata(l)
        '                    Catch ex As Amazon.S3.AmazonS3Exception
        '                        If ex.ErrorCode = "NoSuchKey" Then
        '                            Return Nothing
        '                        Else
        '                            Throw ex
        '                        End If
        '                    End Try
        '                End Function)
    End Function
    

    我不知道为什么将同步版本放入线程或使用FromAsync会有什么关系,但显然后者看起来更好看,测试显示它更快。

1 个答案:

答案 0 :(得分:7)

您可以使用WhenAny处理完成后的任务结果:

while (responseTasks.Length > 0)
{
  var completedTask = await Task.WhenAny(responseTasks);
  responseTasks.Remove(completedTask);
  var response = await completedTask;
  ...
}

(对不起C#;我的VB语法错误太久了。)

有关该主题的完整讨论,请参阅Stephen Toub's post on the subject

另一个选项是TPL Dataflow,它允许你构建一个&#34;网格&#34;数据通过。对于此示例,Dataflow可能过度,但如果您的实际处理更复杂,则它非常有用。

就MD5而言,使其异步不应成为问题。基于异步I / O的任务(例如GetObjectMetadataAsync返回的任务)不使用线程池线程。我尝试了其他一些场景(比如自己异步运行MD5)然后发布另一个问题,如果没有明显的问题出现。