1

いくつかの並列タスクがいつ完了するか知りたいです。

私はこのコードを使用して、Webサイトで10秒のHttpRequestタイムアウトを使用して1500〜2000の小さなWebClient.DownloadStringを作成しています。

Task.Factory.StartNew(() => 
    Parallel.ForEach<string>(myKeywords, new ParallelOptions 
    { MaxDegreeOfParallelism = 5 }, getKey));

クエリが失敗して例外が発生し、関数が終了しない場合や、各getKey関数内のUIの更新が2回呼び出されているように見える場合があるため、完了したタスクの数を正確に把握できません。私が計算しているのは、UI更新呼び出しの数/キーワードの総数であり、100%から250%の結果が得られますが、タスクがいつ完了するかはわかりません。私は多くのSOディスカッションを検索しますが、直接的な方法や私のニーズに合った方法はありませんでした。したがって、Framework 4.0はTasks.AllCompletedイベントハンドラーまたは同様の回避策を提供していないと思いますか?

Parallel.ForeachをUIスレッドではなく他の1つのスレッドで実行してから追加する必要がありますか?

myTasks.WaitAll

[編集]

一時的な解決策は、文字列のリストをArrayListにコピーし、各クエリの開始時にリストから各項目を1つずつ削除することでした。関数がうまく機能したかどうかはいつでも、すべてのアイテムがいつ処理されたかを知っています。

4

1 に答える 1

2

Parallel.ForEach例外の処理に関しては、他のループと何ら変わりはありません。例外がスローされると、ループの処理が停止します。これがおそらく、パーセンテージに差異が見られる理由です(ループを処理しているときに、カウントを処理している可能性があると思います)。

また、クラスParallel.ForEachで行っている非同期呼び出しはIO完了(ネットワーク応答)の待機をブロックするため、実際には必要ありません。それらは計算上バインドされていません(計算上バインドされている場合ははるかに優れています)。WebClientParallel.ForEach

そうは言っても、最初に呼び出しをに変換しWebClientて使用する必要がありますTask<TResult>クラスを使用すると、イベントベースの非同期パターンタスクベースの非同期パターンに簡単に変換できます。TaskCompletionSource<TResult>

Uriの呼び出しの結果として生成される一連のインスタンスがあると仮定すると、getKeyこれを行うための関数を作成できます。

static Task<String> DownloadStringAsync(Uri uri)
{
    // Create a WebClient
    var wc = new WebClient();

    // Set up your web client.

    // Create the TaskCompletionSource.
    var tcs = new TaskCompletionSource<string>();

    // Set the event handler on the web client.
    wc.DownloadStringCompleted += (s, e) => {
        // Dispose of the WebClient when done.
        using (wc)
        {
            // Set the task completion source based on the
            // event.
            if (e.Cancelled)
            {
                // Set cancellation.
                tcs.SetCancelled();
                return;
            }

            // Exception?
            if (e.Error != null)
            { 
                // Set exception.
                tcs.SetException(e.Error);
                return;
            }

            // Set result.
            tcs.SetResult(e.Result);
        };

    // Return the task.
    return tcs.Task;
};

上記は、1つ WebClientを使用するように最適化できることに注意してください。これは、演習として残されています(テストで必要であることが示されていると仮定します)。

そこから、次のシーケンスを取得できますTask<string>

// Gotten from myKeywords
IEnumerable<Uri> uris = ...;

// The tasks.
Task<string>[] tasks = uris.Select(DownloadStringAsync).ToArray();

タスクの実行を開始するには、 extensionメソッドを呼び出す必要があることに注意してください。これは、延期された実行を回避するためです。を呼び出す必要はありませんが、リスト全体を列挙してタスクの実行を開始するものを呼び出す必要があります。ToArrayToArray

これらのインスタンスを取得したら、次のように、クラスのメソッドTask<string>を呼び出すことで、すべてのインスタンスが完了するのを待つことができます。ContinueWhenAll<TAntecedentResult>TaskFactory

Task.Factory.ContinueWhenAll(tasks, a => { }).Wait();

tasksこれが行われると、配列を循環し、Exception および/またはResultプロパティを調べて、例外または結果が何であったかを確認できます。

ユーザーインターフェイスを更新する場合は、Enumerable.Selectの呼び出しをインターセプトすることを検討する必要があります。つまり、ダウンロードが完了したときに、のContinueWith<TNewResult>メソッドを呼び出して、次のTask<TResult>ように操作を実行する必要があります。

// The tasks.
Task<string>[] tasks = uris.
    Select(DownloadStringAsync).
    // Select receives a Task<T> here, continue that.
    Select(t => t.ContinueWith(t2 => {
        // Do something here: 
        //   - increment a count
        //   - fire an event
        //   - update the UI
        // Note that you have to take care of synchronization here, so
        // make sure to synchronize access to a count, or serialize calls
        // to the UI thread appropriately with a SynchronizationContext.
        ...

        // Return the result, this ensures that you'll have a Task<string>
        // waiting.
        return t2;
    })).
    ToArray();

これにより、発生したものを更新できます。上記の場合、Select再度呼び出す場合はt2、エラー処理メカニズムの状態に応じて、他のイベントの状態を確認して発生させることができます。

于 2012-10-09T20:29:26.483 に答える