我正在使用线程派对数据模型,该模型使用它的自定义数据模型。数据模型的层次结构如下:
型号
---表(表的类型)
-----行(行的类型)
-------细胞(细胞类型)
Table的属性Rows与DataTable类似,我必须在多个任务中访问此属性。现在我需要表中的一行,其列值为指定值。
为此,我创建了一个具有lock语句的方法,使其只能从一个线程访问一次。
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;
}
要为Table中的一个列创建查找,我创建了一个方法,该方法总是尝试创建行的副本以避免集合修改的异常:
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
但是其他线程可以修改表(例如,添加,删除行或更新任何行中的列值)。我收到以下"收集修改后的例外":
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)
我无法将此第三方库修改为并发集合,并且这个数据模型在多个项目之间共享。
问:我打猎,让我允许在此集合多个读者的解决方案也许是它在另一个线程修改..是否有可能获得集合的副本没有得到例外?? < / p>
参考下面的SO线程,但没有找到确切的解决方案:
Lock vs. ToArray for thread safe foreach access of List collection
Can ToArray() throw an exception?
Is returning an IEnumerable<> thread-safe?
答案 0 :(得分:1)
最简单的解决方案是重试异常,如下所示:
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.
}
}
最终,您将获得不会抛出异常并且数据完整性验证通过的运行。
或者,您可以深入潜水(我的意思是非常非常深)。
免责声明:永远不要在生产中使用它。除非你绝望,否则别无选择。
因此,我们已经确定您正在使用自定义集合(TableRowCollection
),该集合最终使用List<Row>.Enumerator
来遍历行。这有力地表明您的收藏集由List<Row>
支持。
首先,您需要获得对该列表的引用。您的收藏品不会公开曝光,因此您需要进行一些调整。您将需要使用Reflection来查找并获取支持列表的值。我建议您在调试器中查看TableRowCollection
。它将向您展示非公开成员,您将知道要反映的内容。
如果找不到List<Row>
,请仔细查看TableRowCollection.GetEnumerator()
- 具体为GetEnumerator().GetType()
。如果返回List<Row>.Enumerator
,那么bingo:我们可以从中获取支持列表,如下所示:
List<Row> list;
using (IEnumerator<Row> enumerator = table.GetEnumerator())
{
list = (List<Row>)typeof(List<Row>.Enumerator)
.GetField("list", BindingFlags.Instance | BindingFlags.NonPublic)
.GetValue(enumerator);
}
如果上述获取List<Row>
的方法失败,则无需进一步阅读。你不妨放弃。
如果您成功了,现在您已获得支持List<Row>
,我们必须List<T>
查看private T[] _items;
private int _size; // Accessible via "Count".
private int _version;
。
我们看到的是使用了3个字段:
_size - 1
我们的目标是将索引在零到_items
之间的项从_version
数组复制到一个新数组中,并在List<T>
次更改之间执行此操作。
观察线程安全:volatile
不使用锁定,没有任何字段标记为_version
,++
通过Interlocked.Increment
递增,而不是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();
}
}。长话短说这意味着不可能读取所有3个字段值并自信地说我们正在查看稳定数据。我们必须反复阅读字段值,以便有点确信我们正在查看合理的快照(我们永远不会100%自信,但您可能会选择满足于“好”足够“)。
CopyVolatileList
注意重新稳定窗口:它越大,你就越有信心不处理撕裂的读数(因为列表正在修改所有3个字段)。我已经确定了我在测试中无法失败的最小值,我在一个线程上的紧密循环中调用FAVORITE_FILE
,并使用另一个线程将项添加到列表中,删除它们或清除列表0到20ms之间的随机间隔。
如果你删除了稳定窗口,你偶尔会在数组的末尾得到一个带有未初始化元素的副本,因为另一个线程在你复制时删除了一行 - 这就是为什么需要它。
显然,你应该尽可能地验证副本(至少在稳定窗口失败的情况下检查数组末尾未初始化的元素)。
祝你好运。