0

私の入力は、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

2 つの問題があります。

  1. 頂いた順に回答お待ちしております。より速く取得できるように、完了した順に処理したいと思います。

  2. 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 がロックアップしません。ループ全体は 45 秒から約 75 秒かかり、最初のファイルの MD5 結果は 40 秒待った後に発生します。

編集2:

自分に合った解決策を見つけました。問題は私の 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 を使用するかが問題になる理由はわかりませんが、明らかに後者の方が見栄えがよく、テストでははるかに高速であることが示されています。

4

2 に答える 2

7

WhenAny完了時にタスク結果を処理するために使用できます。

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

(C# で申し訳ありません。私の VB 構文が正しくなるには長すぎます)。

このトピックの完全な説明については、件名に関する Stephen Toub の投稿を参照してください。

別のオプションはTPL Dataflowです。これにより、データが通過する「メッシュ」を構築できます。この例では、Dataflow はやり過ぎかもしれませんが、実際の処理がより複雑な場合に役立ちます。

MD5 に関する限り、非同期にすることは問題になりません。非同期 I/O に基づくタスク ( によって返されるタスクなどGetObjectMetadataAsync) は、スレッド プール スレッドを消費しません。他のいくつかのシナリオ (MD5 を単独で非同期に実行するなど) を試してから、明らかな結果が得られない場合は別の質問を投稿します。

于 2012-09-14T19:09:09.680 に答える
0

を使用する代わりに、を使用Task.ContinueWithすると役立つ場合がありますawaitawaitまた、これらのループ内で使用するよりも、コードをもう少し簡単にするのにも役立ちます。

ノート:

明らかに、あなたと同じデータ型を利用できるわけではないので、基本的なデータ型とデバッグ ステートメントを使用して例を作成し、コード例をそれに改造しようとしました。構文を少し調整する必要がある場合があります。

編集:

同期コンテキストを渡すように例を更新してContinueWith、コールバックが UI スレッドで発生するようにしました (この呼び出しコードが UI スレッドで発生していると仮定します)。

参考:UIスレッドでのタスク継続

...

Dim s3client As New AmazonS3Client(KeyId.Text, keySecret.Text)

For Each lvi As ListViewItem In lvStatus.Items

    Dim currentListItem = lvi

    Dim gomr As New Amazon.S3.Model.GetObjectMetadataRequest
    gomr.BucketName = S3FileDialog.GetBucketName(lvi.SubItems(2).Text)
    gomr.Key = S3FileDialog.GetPrefix(lvi.SubItems(2).Text)

    ' Pass this context to the ContinueWith method so that the callback is executed on the UI thread '
    Dim context = TaskScheduler.FromCurrentSynchronizationContext()
    Dim t = s3client.GetObjectMetadataAsync(gomr)
    t.ContinueWith(Sub(task) OnDownloadComplete(task.Result, currentListItem), context)
    t.Start()

Next

...

Private Sub OnDownloadComplete(response As GetObjectMetadataResponse, item As ListViewItem)
    If response.ETag.Trim(""""c) = MD5CalcFile(item.SubItems(1).Text) Then
        item.SubItems(3).Text = "Match"
        UpdateLvi(item)
    End If
End Sub
于 2012-09-14T21:26:34.540 に答える