请注意,ASP.NET 本质上是一个多线程系统。DataView
被记录为对于读取是线程安全的,但对于写入不是。考虑这一点很重要。
DataTable
在呈现的代码片段中,从缓存中拉出一个实例并访问它DefaultView
以进行过滤和写入一个新的实例DataTable
可能会出现一个证明不安全的条件,这不是因为过滤器中的列,而是因为多个可以同时执行相同代码的线程。
作为实现细节,DataView
利用了几位内部状态。在ToTable()
执行过程中,涉及到多个非本地状态。特别是,有一个字典字段作为键在 DataRow 上,还有一个 DataRow 字段用作键!该字典被清除,添加到该行,该行被引用行的值覆盖,然后设置为空,这都是该过程的一部分。当多个线程同时执行此操作时,一个线程正在覆盖并使另一个线程所依赖的状态无效并非不可想象。这可能导致问题中提到的异常以及其他潜在的有害后果。
无论如何,让我们尝试使用代码片段作为起点来重现该问题,同样是在可以利用多次执行的环境中。
static void Main()
{
var table = new DataTable();
table.Columns.Add("Foo");
table.Columns.Add("ID", typeof(int));
for (int i = 0; i < 100; i++)
{
table.Rows.Add(i.ToString(), i);
}
for (int j = 0; j < 100; j++)
{
Enumerable
.Range(0,100)
.AsParallel()
.ForAll(item => ExecuteToTable(table, item));
}
}
static void ExecuteToTable(DataTable table, int item)
{
var view = table.DefaultView;
view.RowFilter = string.Format("Foo = '{0}'", item);
var filteredTable = view.ToTable();
}
这会产生异常吗?跑起来看看!它可能需要多次执行,但如果运行代码的机器与我的机器相似,则不需要很多。(通过并行查询的循环,对我来说真的只需要一次。)
我已经在 LinqPad 中运行了这段代码,并且已经生成了“期望的”异常。由于这是一个并行查询执行,它将被包装在 AggregatedException 中,但 InnerException 会说明问题。
KeyNotFoundException:给定的键不在字典中。
at System.Collections.Generic.Dictionary`2.get_Item(TKey key)
at System.Data.DataView.CopyTo(DataRowView[] array, Int32 index)
at System.Data.DataView.GetEnumerator()
at System.Data.DataView.ToTable(String tableName, Boolean distinct, String[] columnNames)
at System.Data.DataView.ToTable()
所以我们已经复制了它,并希望能理解它。怎么避免呢?有几种可能的解决方案,根据您的用例,有些解决方案比其他解决方案更可口,范围可以从大规模重写到细微的更改。
你可以
在访问 DefaultViewtable.Copy()
之前使用复制 DataTable 。这将为每个请求提供自己的表(在上面的代码段中)。但是,如果表很大,复制可能会很昂贵。尝试使用运行上述重现代码Copy()
并查看是否避免了异常。
完全避免使用 DataView。Linq 对于 DataTables 也很有用。下面的代码片段可用于生成过滤的 DataTable 输出。但是,还要注意,如果没有行通过过滤器,它CopyToDataTable()
可能会抛出自己的异常。如果有可能发生这种情况,请在调用最后一部分之前拆分代码并检查结果(使用 .Any())。与 the 相比的另一个缺点是,如果您使用可用的重载DataView
,则使用 aDataView
允许您指定希望包含在输出表中的列。ToTable
var filteredTable
= table.AsEnumerable()
.Where(row => string.Equals(row.Field<string>("Foo"), item.ToString(), StringComparison.InvariantCultureIgnoreCase))
.CopyToDataTable();
当然,您可以进一步探索重新设计代码,将您自己的线程安全措施添加到缓存策略等,尽管这些将花费更多时间来实现更改实际表的过滤方式。