3

私は古き良きInvalidOperationException標準メッセージでスローされています

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

問題は、列挙子がそれ自体を変更していないことです。次に例を示します。

private TRoute _copyRoute(TRoute route)
{
    TRoute tempRoute = new TRoute();
    tempRoute.Initialize(route.Resource);

    foreach (TVisit visit in route)
    {
       tempRoute.Add(visit);
    }
    tempRoute.EndLocation = route.EndLocation;
    return tempRoute;
}

私のコードはマルチスレッド(この例では約12〜15スレッド)であり、各スレッドはルートの独自のディープクローンで動作することになっています。明らかにどこかで問題が発生していますが、私の質問は、これほど多くのスレッドでこれを追跡するにはどうすればよいですか?数を減らすと、問題が顕在化するのを大幅に防ぐことができます。

この場合、私のルートインスタンスはIListであるため、インターフェイスに何かを追加して遊ぶことができます。その下には、独自のリスト実装があります。

編集

追加するだけで、これをToArray()またはToList()して、ここでの問題を無視することもできますが、実際にはそうしたくありません。原因を突き止めたいと思います。例えば:

次のように変更すると:

private TRoute _copyRoute(TRoute route)
{
    TRoute tempRoute = new TRoute();
    tempRoute.Initialize(route.Resource);

    foreach (TVisit visit in route.ToList())
    {
       tempRoute.Add(visit);
    }
    tempRoute.EndLocation = route.EndLocation;
    return tempRoute;
}

次に、ToList()の直前にチャンスが発生したため、このアサートに失敗します...その変更がどこで発生しているかを確認する必要があります

TRoute tempRoute1 = CopyRoute(route1);
TRoute tempRoute2 = CopyRoute(route2);
Debug.Assert(tempRoute1.Count == route1.Count);
4

4 に答える 4

5

これがあなたをラップするために使うことができるものですIList<T>-それはそれが各書き込み操作で正しいスレッドにあることをチェックします。もちろん、あるスレッドで別のスレッドに書き込んでいるときにこれを繰り返すのは安全ではありませんが、それは問題ではないと思います。(書き込み操作だけでなく、すべての操作をCheckThreadいつでも呼び出すことができます。)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Threading;

class ThreadAffineList<T> : IList<T>
{
    private readonly Thread expectedThread;
    private readonly IList<T> list;

    public ThreadAffineList(IList<T> list)
    {
        this.list = list;
        this.expectedThread = Thread.CurrentThread;
    }

    private void CheckThread()
    {
        if (Thread.CurrentThread != expectedThread)
        {
            throw new InvalidOperationException("Incorrect thread");
        }
    }

    // Modification methods: delegate after checking thread
    public T this[int index]
    {
        get { return list[index]; }
        set
        {
            CheckThread();
            list[index] = value;
        }
    }

    public void Add(T item)
    {
        CheckThread();
        list.Add(item);
    }

    public void Clear()
    {
        CheckThread();
        list.Clear();
    }

    public void Insert(int index, T item)
    {
        CheckThread();
        list.Insert(index, item);
    }

    public bool Remove(T item)
    {
        CheckThread();
        return list.Remove(item);
    }

    public void RemoveAt(int index)
    {
        CheckThread();
        list.RemoveAt(index);
    }

    // Read-only members
    public int Count { get { return list.Count; } }
    public bool IsReadOnly { get { return list.IsReadOnly; } }

    public IEnumerator<T> GetEnumerator()
    {
        return list.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }

    public bool Contains(T item)
    {
        return list.Contains(item);
    }

    public void CopyTo(T[] array, int arrayIndex)
    {
        list.CopyTo(array, arrayIndex);
    }

    public int IndexOf(T item)
    {
        return list.IndexOf(item);
    }
}
于 2011-08-15T16:37:45.180 に答える
1

基になるコレクションのAdd(TVisit)/Remove(TVisit)を制御していると仮定します。TRoute

  1. を拡張してTRoute.IEnumerator<TVisit> GetEnumerator()、AutoResetEventまたはMutexを設定します
  2. Add(TVisit)/メソッドを拡張して、Remove(TVisit)タイムアウトなしでイベント/ミューテックスを待機します

    if(!autoReseEvent.WaitOne(0)) throw new MyException();
    
  3. キャッチMyExpcetionすると、スタックトレースが取得され、原点が変更されます。

更新: このアプローチの問題は、イベント/ミューテックスをいつリリースするかです。おそらく、列挙子を次のような新しいクラスで装飾する必要があります。

public IEnumerator<TVisit> GetEnumerator()
{
    IEnumerator<TVisit> originEnum = // get it somehow from underlying collection
    IEnumerator<TVisit> evenlope = new DisposableEvenlope<TVisit>(originEnum);
    evenlope.Disposed += new EventHandler(/* do your magic and reset event/mutex here */);
    return evenlope;
}

そして、evelope自体:

public class DisposableEvenlope<T> : IEnumerator<T>
{
    private IEnumerator<T> _privateEnum;

    public event System.EventHandler Disposed;

    public DisposableEvenlope(IEnumerator<T> privateEnum)
    {
        _privateEnum = privateEnum;
    }

    public T Current
    {
        get { return _privateEnum.Current; }
    }

    public void Dispose()
    {
        Disposed(this, new System.EventArgs());
    }

    object IEnumerator.Current
    {
        get { return _privateEnum.Current; }
    }

    public bool MoveNext()
    {
        return _privateEnum.MoveNext();
    }

    public void Reset()
    {
        _privateEnum.Reset();
    }
}
于 2011-08-12T12:31:57.940 に答える
0

列挙中にコレクションを変更していないため、問題は明らかに作成したコードにはありません。

どういうわけか、2つのことのうちの1つが起こっています:

  1. あなたrouteはどういうわけか深いクローンではなく、いくつかのスレッドはあなたが貼り付けたロジックに渡しているのと同じコレクションを変更しています(それはどこかのコーディングの問題であり、厄介な競合状態ではないので追跡しやすいはずです)。
  2. 例外は、貼り付けたコードではなく、クローン作成ロジックで正確に発生します。

コレクションのクローンを作成しながら、いくつかのロックメカニズムを実装してみて、それで問題が解決するかどうかを確認してください。

于 2011-08-12T12:13:41.137 に答える
0

複数のスレッドが接触する可能性があることを知っているのでroute、ルート上での接触を防止しlock()、常にlock()変換route.ToArray()を行ってから、その配列のforループを使用します。これは、ループ全体をlock()すると、パフォーマンス上の欠点を感じる可能性があるためです。コレクションに実際に触れているのは誰かを把握するために、コレクションを派生させ、add/remove要素のローカルメンバー変数でスレッドIDを追跡できます。

于 2011-08-12T13:03:07.853 に答える