Исключение Ninject в Parallel.Foreach

У меня есть фрагмент кода, который запускает Parallel.Foreach в списке элементов для обработки. Каждая итерация создает пару объектов, каждый из которых создает и размещает собственный экземпляр Ninject IKernel. IKernel удаляется, когда объект выполняет свою работу.

Тем не менее, этот код отлично работает на моем ноутбуке с Windows 7, I7. Однако, когда я загружаю его на свой VPS, работающий под управлением Windows 2008, я получаю это исключение. Исключение не возникает на одной итерации, иногда оно проходит через 10 итераций и выдает исключение, в других случаях оно проходит через сотни из них. Очевидно, это похоже на проблему с потоками, но это не происходит нигде, кроме моего VPS. Если это имеет значение, это размещается в ASP.NET IIS.

System.AggregateException: One or more errors occurred. --->
System.ArgumentOutOfRangeException: Index was out of range. 
    Must be non-negative and less than the size of the collection.  
    Parameter name: index
        at System.Collections.Generic.List`1.RemoveAt(Int32 index)
        at Ninject.KernelBase.Dispose(Boolean disposing)

Вот фрагмент кода:

//Code that creates and disposes the Ninject kernel
using(ninjectInstance = new NinjectInstance())
{
    using (var unitOfWork = ninjectInstance.Kernel.Get<NinjectUnitOfWork>())
    {
        Init();
        continueValidation = Validate(tran, ofr);
    }
}

public class NinjectInstance : IDisposable
{
    public IKernel Kernel { get; private set; }

    public NinjectInstance() 
    {           
        Kernel = new StandardKernel(
           new NinjectSettings() { AllowNullInjection = true }, 
           new NinjectUnitOfWorkConfigModule());          
    }

    public void Dispose()
    {
        if (Kernel != null)
        {
            Kernel.Dispose();
        }
    }
}   

Редактировать 1 Одно можно сказать наверняка: это проблема безопасности потоков, и я не должен создавать более одного экземпляра IKernel для каждого приложения. Это вопрос понимания того, как настроить правильные области для обеспечения безопасности потоков Entity Framework Context, сохраняя при этом подход типа UoW, когда несколько классов бизнес-уровня могут совместно использовать один и тот же контекст EF в пределах области UoW в одном потоке.


person e36M3    schedule 28.04.2011    source источник


Ответы (2)


См. http://groups.google.com/group/ninject/browse_thread/thread/574cd317d609e764

Как я уже говорил, ctor Ninject не является потокобезопасным банкоматом, если только вы не используете NOWEB! Если создавать/удалять ядро ​​так много раз, вам придется синхронизировать доступ самостоятельно! Я все же предлагаю перепроектировать вашу реализацию UoW!

person Remo Gloor    schedule 29.04.2011
comment
Спасибо, Ремо, мы работаем над этим вместе с вами в группе пользователей Google. Я обновлю здесь правильный ответ, как только пойму, что это такое. - person e36M3; 29.04.2011

Похоже, что ninjectInstance — это переменная экземпляра. Следовательно, возможно, что в параллельной среде ninjectInstance.Dispose() будет вызываться дважды (вызов Kernel.Dispose() не устанавливает для свойства ядра значение null) для одного и того же экземпляра, а поскольку Kernel.Dispose() уже был вызван, метод завершится ошибкой.

Может быть, вы хотели что-то вроде

using (var ninjectInstance = new NinjectInstance()) {
..
}
person PoppaVein    schedule 28.04.2011
comment
Я не думаю, что это возможно, поскольку только один поток (тот, который обрабатывает конкретную итерацию Parallel.Foreach) создает объект, который имеет ninjectInstance уровня класса, который затем используется в операторе using. Как только происходит следующая итерация, этот объект уже выходит за рамки, следующая итерация создает свой собственный набор. Причина, по которой это переменная экземпляра и область действия класса, заключается в том, что она позволяет классам, наследуемым от этого объекта, легко использовать base.ninjectInstance.Get‹› в методах Init() и Validate(). Мне не нужно передавать его в качестве параметра. - person e36M3; 28.04.2011
comment
Кстати, на всякий случай я установил Kernel = null сразу после Kernel.Dispose(), та же проблема. - person e36M3; 28.04.2011
comment
-1 Хотя вы просто пытаетесь помочь, это не имеет ничего общего с основной проблемой и не добавляет ничего фактического к дискуссии, извините... - person Ruben Bartelink; 29.04.2011
comment
@ Рубен, почему бы тебе не рассказать нам, в чем основная проблема, и почему мой ответ не имеет к этому никакого отношения? - person PoppaVein; 02.05.2011
comment
@Remo Gloor уже позаботился об этом - код, опубликованный OP, в порядке и правильно использует шаблон Dispose, даже если могут быть второстепенные элементы стиля. Проблема заключается в относительно неожиданном отсутствии безопасности потоков в реализации ninject. Предполагать, что перенастройка, как вы предлагаете, будет иметь существенное значение, неверно. Мне не нужно никого учить, я просто указываю на то, что неправильно, чтобы люди, которые могут быть не знакомы с деталями, не путали предположения с фактами. Перечитывая, я вижу, что это чрезмерная реакция на ваш вопрос, но.... - person Ruben Bartelink; 04.05.2011