Ошибка DataBind () в Parallel.Invoke из-за пустого стека.ошибка - PullRequest
2 голосов
/ 28 июля 2011

Я не уверен, почему эта ошибка происходит периодически. У меня есть 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)

Кто-нибудь знает, почему это происходит и как этого избежать?

Ответы [ 2 ]

2 голосов
/ 28 июля 2011

Это проблема параллелизма. Методы экземпляра на веб-элементах управления не гарантируют безопасность типов . В результате 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(); 
        }
    }
}

Обычно каждое нажатие сопровождается более поздним всплытием, гарантирующим, что стек никогда не будет пустым. Однако сам класс Stack основан на массивах, и массив копируется в больший массив при заполнении. Если при копировании массива одновременно выдвигается несколько значений, операция копирования в некоторых случаях может выполняться дважды, при этом почти все данные теряются. Когда это происходит в контексте PushDataBindingContext, элемент данных фактически никогда не помещается в стек - когда метод позже пытается извлечь элемент, который он вытолкнул из стека, стек пуст и выдается исключение. 1012 *

1 голос
/ 28 июля 2011

Исключением является проблема безопасности потока.В частности, если у вас есть несколько потоков, выполняющих код, подобный этому, параллельно:

// 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();
}

Это предотвратит удаление нескольких потоковпредметы из стопки одновременно.

...