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.
Wenn ich Sie richtig verstehe, könnten Sie eine neue Liste mit den Zeilen erstellen und diese verwenden. 'var rows = new Liste (GetTableRows());' –
Domysee
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. –
@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. –