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)

有人知道为什么会发生这种情况以及如何避免吗?

Parallel中的DataBind().由于栈空导致调用失败.错误

这是并发性问题。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.Counts.Pop()调用之间的另一个线程清空。由于堆栈为空,后续对s.Pop()的调用失败。

c# 4中的一个(推荐的)替代方案是使用System.Collections.Concurrent.ConcurrentStack代替Stack。这个类包括TryPop方法,如果堆栈不为空,它将返回(作为输出参数)堆栈中的顶部项,否则返回false。因为进程是原子的,所以操作是线程安全的。

第二个选项是使用SyncRoot属性锁定堆栈:
lock(s.SyncRoot)
{
    if (s.Count > 0)
       s.Pop();
}

这将防止多个线程同时从堆栈中删除项。