我的输入是位于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
我有两个问题:
我正按照我制作的顺序等待回复。我宁愿按照他们完成的顺序处理它们,以便让它们更快。
MD5计算是长且同步的。我尝试将其设为异步,但进程已锁定。我认为MD5任务被添加到.Net的任务列表的末尾,并且在完成所有下载之前它没有运行。
理想情况下,我会在响应到达时处理响应,而不是按顺序处理,并且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会有什么关系,但显然后者看起来更好看,测试显示它更快。
答案 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)然后发布另一个问题,如果没有明显的问题出现。