10

この質問に対する答えは優れていますが、同時実行のために List.ToArray() の呼び出しをロックで囲む必要があることを意味します。 このブログ投稿は、壊滅的に失敗する可能性があることも示唆しています (ただし、めったにありません)。「コレクションが変更されました。列挙が完了しない可能性があります」例外を回避するために、リストまたはその他のコレクションを列挙するときは、通常、ロックインではなく ToArray を使用します。この回答とブログ投稿は、その仮定に疑問を投げかけています。

List.ToArray() のドキュメントには例外がリストされていないため、(古いデータを使用している可能性がありますが) 常に完了すると常に想定しており、データの一貫性の観点からはスレッドセーフではありませんが、スレッドですコード実行の観点から安全です。つまり、例外をスローせず、呼び出しによって基になるコレクションの内部データ構造が破損することはありません。

この仮定が正しくない場合、問題が発生したことはありませんが、高可用性アプリケーションでは時限爆弾になる可能性があります。決定的な答えは何ですか?

4

5 に答える 5

7

ToArray1 つの単純な理由から、メソッドの例外の可能性に関するドキュメントは見つかりません。これは、多くの「オーバーロード」を持つ拡張メソッドです。それらはすべて同じメソッド シグネチャを持ちますが、実装はコレクション タイプごとに異なります (例:List<T>HashSet<T>.

ただし、ほとんどのコードでは、.NET フレームワーク BCL がパフォーマンス上の理由からロックを実行しないという安全な仮定を立てることができます。ToListforの非常に具体的な実装も確認しましたList<T>

public T[] ToArray()
{
    T[] array = new T[this._size];
    Array.Copy(this._items, 0, array, 0, this._size);
    return array;
}

ご想像のとおり、最終的に で実行されるのは非常に単純なコードですmscorlibこの特定の実装では、Array.Copy メソッドの MSDN ページで発生する可能性のある例外も確認できます。宛先配列が割り当てられた直後にリストのランクが変更された場合にスローされる例外に要約されます。

これは些細な例であることを念頭に置くとList<T>、配列に格納するためにより複雑なコードを必要とする構造体で例外が発生する可能性が高くなることが想像できます。の実装Queue<T>は、失敗する可能性が高い候補です。

public T[] ToArray()
{
    T[] array = new T[this._size];
    if (this._size == 0)
    {
        return array;
    }
    if (this._head < this._tail)
    {
        Array.Copy(this._array, this._head, array, 0, this._size);
    }
    else
    {
        Array.Copy(this._array, this._head, array, 0, this._array.Length - this._head);
        Array.Copy(this._array, 0, array, this._array.Length - this._head, this._tail);
    }
    return array;
}
于 2013-02-18T22:16:19.050 に答える
5

スレッドセーフがドキュメントによって明示的に保証されていない場合、または原則としてそれを想定することはできません。想定しているとしたら、デバッグ不可能で、生産性/可用性/お金を大量に消費する可能性のあるクラスのバグを本番環境に配置するリスクがあります。あなたはそのリスクを冒しても構わないと思っていますか?

スレッドセーフであると何かをテストすることはできません。あなたは決して確信することはできません。将来のバージョンが同じように動作することを確認することはできません。

それを正しい方法で行い、ロックします。

ところで、これらの発言はList.ToArray、のより安全なバージョンの1つですToArray。リストへの書き込みと同時に使用できると誤解する理由を理解しています。もちろん、それは基礎となるシーケンスのプロパティであるため、IEnumerable.ToArray スレッドセーフになることはできません。

于 2013-02-18T22:12:07.513 に答える
3

ToArray はスレッドセーフではなく、このコードがそれを証明しています!

このややばかげたコードを考えてみましょう:

        List<int> l = new List<int>();

        for (int i = 1; i < 100; i++)
        {
            l.Add(i);
            l.Add(i * 2);
            l.Add(i * i);
        }

        Thread th = new Thread(new ThreadStart(() =>
        {
            int t=0;
            while (true)
            {
                //Thread.Sleep(200);

                switch (t)
                {
                    case 0:
                        l.Add(t);
                        t = 1;
                        break;
                    case 1:
                        l.RemoveAt(t);
                        t = 0;
                        break;
                }
            }
        }));

        th.Start();

        try
        {
            while (true)
            {
                Array ai = l.ToArray();

                //foreach (object o in ai)
                //{
                //    String str = o.ToString();
                //}
            }
        }
        catch (System.Exception ex)
        {
            String str = ex.ToString();                 
        }

    }

行のために、このコードは非常に短い実行で失敗しl.Add(t)ます。はスレッドセーフではないため、ToArray配列を の現在のサイズに割り当て、 (他のスレッドで) にl要素を追加します。l次に、 の現在のサイズをコピーしようとしますが、llaiが多すぎるため失敗します。要素。ToArrayをスローしArgumentExceptionます。

于 2013-02-18T22:26:12.113 に答える
1

次の 2 つのことを混同しているようです。

  • List<T> は、列挙中の変更をサポートしていません。リストを列挙するとき、列挙子は各反復後にリストが変更されているかどうかを確認します。リストを列挙する前に List<T>.ToArray を呼び出すと、リスト自体ではなくリストのスナップショットを列挙しているため、この問題は解決します。

  • List<T> はスレッド セーフなコレクションではありません。上記はすべて、同じスレッドからのアクセスを前提としています。2 つのスレッドからリストにアクセスするには、常にロックが必要です。List<T>.ToArray はスレッドセーフではないため、ここでは役に立ちません。

于 2013-02-18T22:18:32.883 に答える
1

First of all you will have to make it clear that the callsite must be in a thread-safe region. Most regions in your code will not be threadsafe regions and will assume a single thread of execution at any given time (for most application code). For (a very rough estimate) 99% of all application code this question makes no real sense.

Secondly you will have to make it clear "what" the enumeration function really is, as this will vary on the type of enumeration you are running through - are you talking about the normal linq extension to Enumerations?

Thirdly the link you provide to the ToArray code and the lock statement around it is nonsense at best: Without showing that the callsite also locks on the same collection, it does not guarantee thread safety at al.

And so on.

于 2013-02-18T22:08:37.247 に答える