web-dev-qa-db-ja.com

このParallel.ForEach()スレッドの使用は安全ですか?

基本的に、私はこれを使用しています:

_var data = input.AsParallel();
List<String> output = new List<String>();

Parallel.ForEach<String>(data, line => {
    String outputLine = ""; 
    // ** Do something with "line" and store result in "outputLine" **

    // Additionally, there are some this.Invoke statements for updating UI

    output.Add(outputLine);
});
_

入力は_List<String>_オブジェクトです。 ForEach()ステートメントは、各値に対して何らかの処理を実行し、UIを更新して、結果をoutputListに追加します。これに本質的に何か問題がありますか?

注:

  • 出力順序は重要ではありません

更新:

私が得たフィードバックに基づいて、手動のlockを_output.Add_ステートメントとUI更新コードに追加しました。

21
Chris Laplante

はい; List<T>はスレッドセーフではないため、任意のスレッドからアドホックに(おそらく同時に)追加することはできません。代わりにスレッドセーフリストを使用するか、手動でロックを追加する必要があります。または、Parallel.ToListがあるかもしれません。

また、重要な場合:挿入順序は保証されません。

このバージョンis安全ですが:

var output = new string[data.Count];

Parallel.ForEach<String>(data, (line,state,index) =>
{
    String outputLine = index.ToString();
    // ** Do something with "line" and store result in "outputLine" **

    // Additionally, there are some this.Invoke statements for updating UI
    output[index] = outputLine;
});

ここでは、indexを使用して、並列呼び出しごとに異なる配列インデックスを更新しています。

32
Marc Gravell

これに本質的に何か問題がありますか?

はい、すべてです。これはどれも安全ではありません。リストは複数のスレッドで同時に更新するのは安全ではなく、UIスレッド以外のスレッドからUIを更新することはできません。

11
Eric Lippert

ドキュメント _List<T>_のスレッドセーフについて次のように述べています。

このタイプのパブリック静的(Visual Basicで共有)メンバーはスレッドセーフです。インスタンスメンバーは、スレッドセーフであることが保証されていません。

List(Of T)は、コレクションが変更されていない限り、複数のリーダーを同時にサポートできます。コレクションを介して列挙することは、本質的にスレッドセーフな手順ではありません。列挙型が1つ以上の書き込みアクセスと競合するまれなケースでは、スレッドセーフを確保する唯一の方法は、列挙型全体でコレクションをロックすることです。読み取りと書き込みのために複数のスレッドがコレクションにアクセスできるようにするには、独自の同期を実装する必要があります。

したがって、output.Add(outputLine)notスレッドセーフであり、たとえば、add操作をでラップすることによってスレッドセーフを自分で確保する必要があります。 lockステートメント。

4
Heinzi