Parallel中的DataBind().由于栈空导致调用失败.错误
本文关键字:调用 失败 错误 于栈空 中的 DataBind Parallel | 更新日期: 2023-09-27 18:03:51
我不知道为什么这个错误间歇性地发生。我有一个UserControl是并行数据绑定。代码在90%的情况下工作,但经常会出现数据绑定失败并收到以下错误。
at System.Collections.Stack.Pop()
at System.Web.UI.Control.DataBind(Boolean raiseOnDataBinding)
at System.Web.UI.WebControls.Repeater.CreateItem(Int32 itemIndex, ListItemType itemType, Boolean dataBind, Object dataItem)
at System.Web.UI.WebControls.Repeater.CreateControlHierarchy(Boolean useDataSource)
at System.Web.UI.WebControls.Repeater.OnDataBinding(EventArgs e)
有人知道为什么会发生这种情况以及如何避免吗?
这是并发性问题。web控件上的实例方法不能保证是类型安全的。因此,DataBind(和其他实例方法)不应该在多个线程中同时调用。
至于为什么会发生这种情况:Control类实现包括一个内部Page
实例;这个实例有一个用于数据绑定的内部堆栈。
protected virtual void DataBind(bool raiseOnDataBinding) {
bool inDataBind = false;
if (foundDataItem && (Page != null)) {
Page.PushDataBindingContext(dataItem);
inDataBind = true;
}
try{
//...
} finally {
if (inDataBind) {
Page.PopDataBindingContext();
}
}
}
通常情况下,每次push都会伴随着随后的pop,以确保堆栈永远不会为空。但是,Stack类本身是基于数组的,并且在填充数组时将数组复制到更大的数组中。如果在复制数组时同时推入多个值,那么在某些情况下,复制操作可能会执行两次,除了丢失所有值之外。当这种情况发生在PushDataBindingContext
上下文中时,数据项永远不会被实际推送到堆栈中——当该方法稍后试图从堆栈中弹出它推送的项时,堆栈为空并引发异常。
异常可能是线程安全问题。特别是,如果您有多个线程并行运行如下代码:
// s is a instance of Stack
if (s.Count > 0)
s.Pop()
堆栈可以被s.Count
和s.Pop()
调用之间的另一个线程清空。由于堆栈为空,后续对s.Pop()
的调用失败。
c# 4中的一个(推荐的)替代方案是使用System.Collections.Concurrent.ConcurrentStack
代替Stack
。这个类包括TryPop
方法,如果堆栈不为空,它将返回(作为输出参数)堆栈中的顶部项,否则返回false。因为进程是原子的,所以操作是线程安全的。
SyncRoot
属性锁定堆栈:
lock(s.SyncRoot)
{
if (s.Count > 0)
s.Pop();
}
这将防止多个线程同时从堆栈中删除项。