2016-03-25 2 views
0

Ich verwende ein Thread-Party-Datenmodell, das sein benutzerdefiniertes Datenmodell verwendet. Hierarchie des Datenmodells ist wie folgt:
Modell
--- Tables (Tabellentyp)
----- Reihen (Typ Row)
------- Zellen (Zellart)Erstellen Sie eine Kopie von IEnumerable <T>, um die Sammlung aus verschiedenen Threads zu ändern?

Tabelle hat Eigenschaft Zeilen wie DataTable und ich muss auf diese Eigenschaft in mehr als Aufgaben zugreifen. Jetzt brauche ich eine Zeile aus der Tabelle, die einen Spaltenwert auf den angegebenen Wert hat.

Um dies zu tun, habe ich eine Methode erstellt, die Lock-Anweisung hat, um es von nur einem Thread einmal zugänglich zu machen.

public static Row GetRowWithColumnValue(Model model, string tableKey, string indexColumnKey, string indexColumnValue) 
{ 
    Row simObj = null; 
    lock (syncRoot) 
    { 
     SimWrapperFromValueFactory wrapperSimSystem = new SimWrapperFromValueFactory(model, tableKey, indexColumnKey); 
     simObj = wrapperSimSystem.GetWrapper(indexColumnValue); 
    } 
    return simObj; 
} 

Um die Suche für einen der Spalte in der Tabelle zu erstellen, habe ich eine Methode erstellen haben, die immer versuchen, eine Kopie der Zeilen erstellen Sammlung modifizierte Ausnahme zu vermeiden:

Private Function GetTableRows(table As Table) As List(Of Row) 
    Dim rowsList As New List(Of Row)(table.Rows) 'Case 1 
    'rowsList.AddRange(table.Rows) 'Case 2 
    ' Case 3 
    'For i As Integer = 0 To table.Rows.Count - 1 
    'rowsList.Add(table.Rows.ElementAt(i)) 
    'Next 
    Return rowsList 
End Function 

aber auch andere Themen kann die Tabelle ändern (z. B. Zeilen hinzufügen, entfernen oder den Spaltenwert in beliebigen Zeilen aktualisieren). Ich erhalte unter „Collection modifizierte Ausnahme“:

at System.ThrowHelper.ThrowInvalidOperationException(ExceptionResource resource) 
    at System.Collections.Generic.List`1.Enumerator.MoveNextRare() 
    at System.Collections.Generic.List`1.InsertRange(Int32 index, IEnumerable`1 collection) 

ich diese Dritte Bibliothek gleichzeitige Sammlungen nicht ändern können und das gleiche zwischen mehreren Projekten gemeinsame Datenmodell.

Frage: Ich suche nach der Lösung, die mich erlauben, mehrere Leser auf dieser Sammlung entweder in einem anderen Threads geändert .. Ist es möglich, eine Kopie der Sammlung ohne Ausnahme zu erhalten?

unter SO Fäden referenzierte aber haben exakte Lösung nicht gefunden:
Lock vs. ToArray for thread safe foreach access of List collection
Can ToArray() throw an exception?
Is returning an IEnumerable<> thread-safe?

+0

Wenn ich Sie richtig verstehe, könnten Sie eine neue Liste mit den Zeilen erstellen und diese verwenden. 'var rows = new Liste (GetTableRows());' – Domysee

+1

Sie können keine Sammlung "sperren", die keine integrierte Unterstützung dafür hat (über 'SyncRoot' oder ähnliches). Selbst wenn es Ihnen gelingt, einen Snapshot der Daten zu erstellen (indem Sie eine vollständige Kopie wiederholen, bis Sie schließlich eine Ausnahme erhalten), können Sie sicher sein, dass ein anderer Thread keine Beschädigung durch Ändern der Zeilen-/Zellenwerte verursacht hat Überprüfen Sie nicht, dass die Parallelität die Tabelle selbst überprüft. Dies ist eine knifflige Situation. –

+0

@Domysee: Ich habe es bereits mit 3 Implementierungen getestet und ich mache derzeit, wie Sie vorgeschlagen haben, aber es verwenden auch GetEnumerator-Methode intern das Problem verursacht. –

Antwort

1

Die einfachste Lösung auf Ausnahme erneut zu versuchen, ist, wie folgt aus:

private List<Row> CopyVolatileList(IEnumerable<Row> original) 
{ 
    while (true) 
    { 
     try 
     { 
      List<Row> copy = new List<Row>(); 

      foreach (Row row in original) { 
       copy.Add(row); 
      } 

      // Validate. 
      if (copy.Count != 0 && copy[copy.Count - 1] == null) // Assuming Row is a reference type. 
      { 
       // At least one element was removed from the list while were copying. 
       continue; 
      } 

      return copy; 
     } 
     catch (InvalidOperationException) 
     { 
      // Check ex.Message? 
     } 

     // Keep trying. 
    } 
} 

Schließlich Sie‘ Ich bekomme einen Lauf, bei dem die Ausnahme nicht ausgelöst wird und die Validierung der Datenintegrität besteht.

Alternativ können Sie tief tauchen (und ich meine sehr, sehr tief).

HAFTUNGSAUSSCHLUSS: Verwenden Sie dieses Produkt niemals in der Produktion. Es sei denn du bist verzweifelt und hast wirklich keine andere Möglichkeit.

So haben wir festgestellt, dass Sie mit einer benutzerdefinierten Sammlung (TableRowCollection) arbeiten, die schließlich List<Row>.Enumerator verwendet, um durch die Zeilen zu iterieren. Dies deutet stark darauf hin, dass Ihre Sammlung von einem List<Row> unterstützt wird.

Zuerst müssen Sie einen Verweis auf diese Liste erhalten. Ihre Sammlung wird es nicht öffentlich zugänglich machen, also müssen Sie etwas herumspielen. Sie müssen Reflection verwenden, um den Wert der Sicherungsliste zu ermitteln und abzurufen. Ich empfehle Ihnen, Ihre TableRowCollection im Debugger zu suchen. Es zeigt Ihnen nicht-öffentliche Mitglieder und Sie werden wissen, was Sie zu reflektieren haben.

Wenn Sie Ihre List<Row> nicht finden können, werfen Sie einen genaueren Blick auf TableRowCollection.GetEnumerator() - speziell GetEnumerator().GetType(). Wenn das List<Row>.Enumerator zurückgibt, dann Bingo: können wir die Unterstützung Liste aus ihm heraus, etwa so:

List<Row> list; 

using (IEnumerator<Row> enumerator = table.GetEnumerator()) 
{ 
    list = (List<Row>)typeof(List<Row>.Enumerator) 
     .GetField("list", BindingFlags.Instance | BindingFlags.NonPublic) 
     .GetValue(enumerator); 
} 

Wenn die oben genannten Methoden, um Ihre List<Row> versagt haben, gibt es keine Notwendigkeit, weiter zu lesen ist. Du könntest genauso gut aufgeben.

Wenn Sie erfolgreich waren, jetzt, da Sie die Unterstützung List<Row> haben, müssen wir Reference Source für List<T> betrachten.

Was wir sehen, ist 3 Felder verwendet:

private T[] _items; 
private int _size; // Accessible via "Count". 
private int _version; 

Unser Ziel ist es, die Elemente, deren Indizes zwischen Null und _size - 1 aus dem _items Array in ein neues Array zu kopieren, und so zwischen _version zu tun Änderungen.

Beobachtungen erneut Thread-Sicherheit: List<T> verwendet keine Schlösser, keines der Felder als volatile markiert und _version über ++ erhöht, nicht Interlocked.Increment. Lange Rede, kurzer Sinn bedeutet, dass es unmöglich ist, alle 3 Feldwerte zu lesen und zuversichtlich zu sagen, dass wir stabile Daten betrachten. Wir müssen die Feldwerte wiederholt lesen, um etwas zuversichtlich zu sein, dass wir einen vernünftigen Schnappschuss betrachten (wir werden nie 100% sicher sein, aber Sie können sich mit "gut genug" begnügen).

using System; 
using System.Collections.Generic; 
using System.Linq.Expressions; 
using System.Reflection; 
using System.Threading; 

private Row[] CopyVolatileList(List<Row> original) 
{ 
    while (true) 
    { 
     // Get _items and _size values which are safe to use in tandem. 
     int version = GetVersion(original); // _version. 
     Row[] items = GetItems(original); // _items. 
     int count = original.Count; // _size. 

     if (items.Length < count) 
     { 
      // Definitely a torn read. Copy will fail. 
      continue; 
     } 

     // Copy. 
     Row[] copy = new Row[count]; 

     Array.Copy(items, 0, copy, 0, count); 

     // Stabilization window. 
     Thread.Sleep(1); 

     // Validate. 
     if (version == GetVersion(original)) { 
      return copy; 
     } 

     // Keep trying. 
    } 
} 

static Func<List<Row>, int> GetVersion = CompilePrivateFieldAccessor<List<Row>, int>("_version"); 
static Func<List<Row>, Row[]> GetItems = CompilePrivateFieldAccessor<List<Row>, Row[]>("_items"); 

static Func<TObject, TField> CompilePrivateFieldAccessor<TObject, TField>(string fieldName) 
{ 
    ParameterExpression param = Expression.Parameter(typeof(TObject), "o"); 
    MemberExpression fieldAccess = Expression.PropertyOrField(param, fieldName); 

    return Expression 
     .Lambda<Func<TObject, TField>>(fieldAccess, param) 
     .Compile(); 
} 

Hinweis re Stabilisierung Fenster: Je größer sie ist, desto mehr Vertrauen haben Sie, dass Sie nicht mit einem zerrissenen Lese tun hat (weil die Liste in Prozess ist es, alle drei Felder modifizieren). Ich habe mich auf den kleinsten Wert festgelegt, bei dem ich in meinen Tests CopyVolatileList in einer engen Schleife auf einem Thread nicht verwenden konnte, und einen anderen Thread verwendete, um Elemente zur Liste hinzuzufügen, sie zu entfernen oder die Liste in zufälligen Intervallen zwischen 0 zu löschen und 20ms.

Wenn Sie das Stabilisierungsfenster entfernen, erhalten Sie gelegentlich eine Kopie mit nicht initialisierten Elementen am Ende des Arrays, weil der andere Thread beim Kopieren eine Zeile entfernt hat - deshalb wird es benötigt.

Sie sollten die Kopie, sobald sie erstellt wurde, nach bestem Wissen validieren (zumindest nach nicht initialisierten Elementen am Ende des Arrays suchen, falls das Stabilisierungsfenster ausfällt).

Viel Glück.

+0

Danke für detaillierte Informationen, aber ich werde versuchen, Listenelemente zu bekommen –

Verwandte Themen