9

IEnumerable<T>一部のプログラマーは、 のような実装を渡すよりもパラメーターを渡すほうがよいと主張します。これを行う一般的な理由の 1 つは、他の特定の実装よりもList<T>多くのコレクションと互換性があるため、より多くのシナリオで API をすぐに使用できることです。IEnumerable<T>List<T>

マルチスレッド開発シナリオに飛び込むほど、それもIEnumerable<T>正しいタイプではないと考えるようになり、以下でその理由を説明しようとします。

コレクションの列挙中に次の例外を受け取ったことがありますか?

コレクションが変更されました。列挙操作が実行されない場合があります。(InvalidOperationException)

コレクションが変更されたという例外

基本的にこれの原因は、あるスレッドでコレクションが与えられ、他の誰かが別のスレッドでコレクションを変更することです。

この問題を回避するために、次のようにコレクションをメソッド内の配列に変換することで、列挙する前にコレクションのスナップショットを作成する習慣を身につけました。

static void LookAtCollection(IEnumerable<int> collection)
{   
    foreach (int Value in collection.ToArray() /* take a snapshot by converting to an array */)
    {
        Thread.Sleep(ITEM_DELAY_MS);
    }
}

私の質問はこれです。API を使用するときに呼び出し元がコレクションを配列に変換することを余儀なくされたとしても、原則として列挙型ではなく配列向けにコーディングする方がよいのではないでしょうか?

これはよりクリーンで防弾ではありませんか?(パラメータの型は代わりに配列です)

static void LookAtCollection(int[] collection)
{   
    foreach (int Value in collection)
    {
        Thread.Sleep(ITEM_DELAY_MS);
    }
}

配列は、列挙可能で固定長であるというすべての要件を満たし、呼び出し元は、その時点でコレクションのスナップショットを操作するという事実を認識しているため、より多くのバグを節約できます。

私が今見つけることができる唯一のより良い代替手段は、コレクションがアイテムコンテンツに関して読み取り専用であるため、さらに防弾となる IReadOnlyCollection です。

編集:

@DanielPryden は、非常に優れた記事「Arrays thinks some有害性」へのリンクを提供しました。そして、作家によるコメントは、「完全に変更可能であると同時にサイズが固定されているという、かなり矛盾した特性を持つコレクションはほとんど必要ありません」および「ほとんどの場合、コレクションよりも優れたツールが使用されます。配列。" アレイは私が望んでいたほど特効薬に近いものではないことを確信しました.リスクと抜け穴に同意します. IReadOnlyCollection<T>インターフェイスは、配列と の両方よりも優れた代替手段であると思いますが、IEnumerable<T>今では疑問が残ります。呼び出し先は、IReadOnlyCollection<T>メソッド宣言のパラメータ型? それとも、コレクションを参照するメソッドにどの実装IEnumerable<T>を渡すかを呼び出し元が決定できるようにする必要がありますか?

すべての答えをありがとう。これらの回答から多くのことを学びました。

4

5 に答える 5

4

特に文書化されていない限り、ほとんどのメソッドはスレッドセーフではありません。

この場合、スレッドセーフの面倒を見るのは発信者次第だと思います。呼び出し元がIEnumerable<T>パラメーターをメソッドに渡す場合、メソッドがそれを列挙しても驚くことはほとんどありません。列挙がスレッドセーフでない場合、呼び出し元はスレッドセーフな方法でスナップショットを作成し、スナップショットを渡す必要があります。

ToArray()呼び出し先は、またはを呼び出してこれを行うことはできません。ToList()これらのメソッドはコレクションも列挙し、呼び出し先は、スレッドセーフな方法でスナップショットを作成するために必要なもの(ロックなど)を知ることができません。

于 2013-02-20T12:57:56.270 に答える
4

ここでの本当の「問題」は、T[]おそらく理想よりも具体的なパラメータタイプであり、受信者が任意の要素(望ましい場合とそうでない場合があります)を自由に書き込むことができるのIEnumerable<T>は一般的すぎることです。型安全性の観点から、呼び出し元は実装する任意のオブジェクトへの参照を提供できますIEnumerable<T>が、実際にはそのようなオブジェクトのみが実際に機能し、呼び出し元がそれらがどれであるかを明確に知る方法はありません。

Tが参照型の場合、aT[]は、特定の条件に従って、読み取り、書き込み、および列挙に対して本質的にスレッドセーフになります(たとえば、異なる要素にアクセスするスレッドはまったく干渉しません。あるスレッドが別のスレッドが読み取る時間の前後にアイテムを書き込む場合、列挙すると、後者のスレッドは古いデータまたは新しいデータを参照します。Microsoftのコレクションインターフェイスはいずれも、そのような保証を提供していません。また、コレクションが保証できるかできないかを示す手段も提供していません。

私の傾向は、アイテムを許可するようなメンバーを含むが、スレッドセーフな方法で実行できないようなものを含まないを使用するかT[]、定義することです。IThreadSafeList<T> where T:classCompareExchangeItemInterlocked.CompareExchangeInsertRemove

于 2013-02-20T21:48:32.293 に答える
2

この問題は、スレッドに固有のものではありません。を明示的または暗黙的に呼び出すループ内でコレクションを変更してみてくださいGetEnumerator。結果は同じです。違法な操作です。

于 2013-02-20T12:13:18.617 に答える
1

スレッドが他のスレッドと同じオブジェクトにアクセスすることは決して許可されるべきではありません。常に変数/オブジェクト/配列を複製し、スレッドの暇なときにそれらを解析する必要があります。

終了したら、メインスレッドにコールバックして、スレッドが現在の値を更新/マージできるようにする必要があります。

2 つのオブジェクトが同じオブジェクトに同時に到達できる場合、綱引き、または例外または奇妙な動作が発生します。

次のようなスレッドを想像してください。上司と 2 人の従業員がいます。上司は手元にある手紙をコピーして従業員に渡します。従業員にはそれぞれ独自のタスクがあり、1 つはリストのすべてを収集すること、もう 1 つはリストの項目をサプライヤーに注文する必要があるかどうかを確認することです。彼らはそれぞれ自分の個室に行き、上司はその紙を机の上に置くだけで、時々それをチェックして、現時点でどれだけあるかを誰かに伝えます。

従業員 1 は、注文したオブジェクトを持って戻ってきて、それをロジスティクス部門に渡すディレクターに渡します。1 時間後、従業員 2 が戻ってきて、在庫数の更新されたリストを受け取りました。監督は古いリストを破棄し、新しいリストに置き換えます。

リストが 1 つの場合のシナリオです。

上司も同じように指示します。従業員 1 がリストを受け取り、最初のサプライヤーに電話をかけ始めます。従業員 2 がリストを受け取り、アイテムのピッキングを開始します。ディレクターはリストを見たいと思っていますが、リストがなくなり、リストが占有されているために正確な情報がないため、セールスファネル全体が停止します。最初の従業員は通話を終了し、自分のリストを更新したいと考えています。彼はリストが占有されているためできず、リストがなくなって次の項目に進むことができず、親指をいじったり、発作を起こしたりして立ち往生しています。従業員 2 がリストの準備ができたとき、従業員はリストを取り、注文ステータスの呼び出しを終了しようとしますが、ディレクターが押し寄せてリストを取り、情報を営業チームに渡します。

そのため、スレッドがそのリソースに排他的にアクセスする必要があることだけがわかっている場合を除き、常にスレッド内の独立したコピーで作業する必要があります。CPU のタイミングを制御しないため、その特定のスレッドがいつそのリソースで何かを行うかがわからないためです。 .

これが少し役立つことを願っています。

于 2013-02-20T12:22:06.317 に答える
0

背後にある考え方の 1 つは、クエリIEnumerableの結果であるということです。LINQクエリは実行されたコードではなく宣言であるため、 の結果は、または他の構造体IEnumerableで反復処理中foreachまたはアイテムをコピー中にのみ呼び出されます。Array結果を のような静的構造に格納したい場合は、Array反復の前にそれを更新する必要があります。そうIEnumerableしないと、古いデータを受け取ることになります。IEnumerable.ToArray手段:コレクションを更新し、その静的コピーを作成します。したがって、私の答えは、コレクションを操作する前に更新を行う必要がある場合は、IEnumerableより簡潔であるということです。他のシナリオではArray、コードを簡素化するために使用できます。

于 2013-02-20T12:24:04.740 に答える