Область действия Ninject при параллельном запуске Azure Webjob

У меня есть консольное приложение в качестве веб-задания для обработки уведомлений внутри моего приложения. Процессы запускаются с помощью очереди. Приложение взаимодействует с базой данных SQL Azure с помощью entity framework 6. Вызываемый метод Process () считывает / записывает данные в базу данных.

Я получаю несколько ошибок при обработке сообщений очереди. Они никогда не попадают в очередь на яд, так как успешно повторно обрабатываются через 2-3 раза. В основном ошибки следующие:

Необработанное исключение типа System.StackOverflowException в mscorlib.dll

Ошибка: System.OutOfMemoryException: исключение типа «System.OutOfMemoryException».

Размер пакета по умолчанию - 16, поэтому сообщения обрабатываются параллельно.

Я предполагаю, что настройка Ninject для параллельной обработки сообщений неверна. Следовательно, когда они обрабатываются одновременно, возникают некоторые ошибки, и в конечном итоге они обрабатываются успешно.

Мой вопрос: выглядит ли эта установка нормально? Должен ли я использовать InThreadScope (), возможно, поскольку я не знаю, что параллельная обработка также является многопоточной.

Вот код моего приложения.

Program.cs


namespace NotificationsProcessor
{
    public class Program
    {
        private static StandardKernel _kernel;
        private static void Main(string[] args)
        {
            var module = new CustomModule();
            var kernel = new StandardKernel(module);
            _kernel = kernel;

            var config =
                new JobHostConfiguration(AzureStorageAccount.ConnectionString)
                {
                    NameResolver = new QueueNameResolver()
                };
            var host = new JobHost(config);
            //config.Queues.BatchSize = 1; //Process messages in parallel
            host.RunAndBlock();
        }

        public static void ProcessNotification([QueueTrigger("%notificationsQueueKey%")] string item)
        {
            var n = _kernel.Get<INotifications>();
            n.Process(item);
        }

        public static void ProcessPoison([QueueTrigger("%notificationsQueueKeyPoison%")] string item)
        {
            //Process poison message. 
        }
    }
}

Here's the code for Ninject's CustomModule


namespace NotificationsProcessor.NinjectFiles
{
    public class CustomModule : NinjectModule
    {
        public override void Load()
        {
            Bind<IDbContext>().To<DataContext>(); //EF datacontext
            Bind<INotifications>().To<NotificationsService>();
            Bind<IEmails>().To<EmailsService>();
            Bind<ISms>().ToSmsService>();
        }
    }
}

Код для метода процесса.


    public void ProcessMessage(string message)
    {
        try
        {
            var notificationQueueMessage = JsonConvert.DeserializeObject<NotificationQueueMessage>(message);
            //Grab message and check if it has to be processed
            var notification = _context.Set().Find(notificationQueueMessage.NotificationId);

            if (notification != null)
            {
                if (notification.NotificationType == NotificationType.AppointmentReminder.ToString())
                {
                    notificationSuccess = SendAppointmentReminderEmail(notification); //Code that sends email using the SendGrid Api
                }
            }
        }
        catch (Exception ex)
        {
            _logger.LogError(ex + Environment.NewLine + message, LogSources.EmailsService);
            throw;
        }                   
    }


Обновление - добавлено исключение

Исключение создается сериализатором Json. Вот трассировка стека:

Error: System.OutOfMemoryException: Exception of type ‘System.OutOfMemoryException’ was thrown. at System.String.CtorCharCount(Char c, Int32 count) at Newtonsoft.Json.JsonTextWriter.WriteIndent() at Newtonsoft.Json.JsonWriter.AutoCompleteClose(JsonContainerType type) at Newtonsoft.Json.JsonWriter.WriteEndObject() at Newtonsoft.Json.JsonWriter.WriteEnd(JsonContainerType type) at Newtonsoft.Json.JsonWriter.WriteEnd() at Newtonsoft.Json.JsonWriter.AutoCompleteAll() at Newtonsoft.Json.JsonTextWriter.Close() at Newtonsoft.Json.JsonWriter.System.IDisposable.Dispose() at Newtonsoft.Json.JsonConvert.SerializeObjectInternal(Object value, Type type, JsonSerializer jsonSerializer) at Newtonsoft.Json.JsonConvert.SerializeObject(Object value, Type type, Formatting formatting, JsonSerializerSettings settings) at Core.Services.Communications.EmailsService.SendAppointmentReminderEmail(Notificaciones email) in c:\ProjectsGreenLight\EAS\EAS\EAS\Core\Services\Communications\EmailsService.cs:line 489 at Core.Services.Communications.EmailsService.ProcessMessage(String message) in c:\ProjectsGreenLight\EAS\EAS\EAS\Core\Services\Communications\EmailsService.cs:line 124 at Core.Services.NotificacionesService.Process(String message) in c:\ProjectsGreenLight\EAS\EAS\EAS\Core\Services\NotificacionesService.cs:line 56

person lopezbertoni    schedule 20.10.2014    source источник
comment
Без кода в INotifications.Process трудно сказать, что там происходит - нет ничего явно неправильного в том, что вы опубликовали. Изучите код в Process, чтобы увидеть, есть ли бесконечная рекурсия или что-то, что может вызвать нехватку памяти.   -  person Victor Hurdugaci    schedule 21.10.2014
comment
Обновил вопрос методом. Что странно, после повторной попытки сообщение проходит через 2–3 попытки. Вот почему я думаю, что, возможно, конфигурация Ninject вызывает проблемы.   -  person lopezbertoni    schedule 21.10.2014
comment
Связанный: stackoverflow.com/a/14592419/264697   -  person Steven    schedule 21.10.2014


Ответы (1)


Поскольку вы получаете OutOfMemoryExceptions и StackOverflowExceptions, я предполагаю, что может быть рекурсивный или глубоко вложенный метод. Было бы чрезвычайно полезно, если бы у вас была трассировка стека для исключения, к сожалению, это не относится к StackOverflowExceptions. Однако OutOfMemoryException имеет трассировку стека, поэтому вам нужно зарегистрировать это и посмотреть, что в нем написано / добавить это к вашему вопросу. Кроме того, что касается StackoverflowException, вы также можете попробовать this.

Определение объема

Вы не должны использовать .InThreadScope() в таком сценарии. Почему? обычно используется пул потоков. Потоки используются повторно. Это означает, что объекты с областью видимости живут дольше, чем при обработке отдельных сообщений. В настоящее время вы используете .InTransientScope() (это значение по умолчанию, если вы не укажете ничего другого). Поскольку вы используете IDbContext только в одном месте, это нормально. Если вы хотите использовать один и тот же экземпляр для нескольких объектов, вам придется использовать область видимости или передавать экземпляр вручную.

Что может быть проблематичным в вашем случае, так это то, что вы можете создавать много новых IDbContext, но не избавляться от них после того, как вы их использовали. В зависимости от других факторов это может привести к тому, что сборщику мусора потребуется больше времени для очистки памяти. См. Есть ли у меня вызвать dispose в DbContext.

Однако я согласен, что это не решит вашу проблему. Это может просто ускорить работу вашего приложения. Вот как это все равно можно сделать:

Что вам следует сделать: Сделайте INotifications производным от IDisposable:

public interface INotifications : IDisposable 
{
    (...)
}

internal class Notifications : INotifications
{
    private readonly IDbContext _context;

    public Notifications(IDbContext context)
    {
        _context = context;
    }

    (...)

    public void Dispose()
    {
        _context.Dispose();
    }
}

и измените свой код вызова, чтобы избавиться от INotifications:

    public static void ProcessNotification([QueueTrigger("%notificationsQueueKey%")] string item)
    {
        using(var n = _kernel.Get<INotifications>())
        {
           n.Process(item);
        }
    }
person BatteryBackupUnit    schedule 21.10.2014
comment
Обычно я думал, что это последнее средство, включающее ссылки на мою выбранную библиотеку DI вне корня композиции (ссылка на _kernel выше). Я был в подобных ситуациях раньше, и моим решением было ввести фабрику ресурсов. Блок using () все равно будет применяться, но не будет ссылки на контейнер. Мысли? - person Jeremy F; 30.10.2014
comment
@Jeremy F Вместо IResolutionRoot.Get<T> вы можете ввести Func<T>, который поддерживается по крайней мере Ninject (requries ninject.extensions.factory), Unity и AutoFac .. и, я думаю, еще несколькими. Однако мне самому не нравится Func<..>, и я обычно полагаюсь на фабрики, автоматически сгенерированные из интерфейсов (привязка ninject .ToFactory()). Я также реализовал это для Unity, и я думаю, что реализация его для многих других тоже будет возможна. - person BatteryBackupUnit; 31.10.2014
comment
добавление: если все, что вам нужно сделать, это создать некую фабричную реализацию, которая сама использует IResolutionRoot, вы не удалите ссылку на контейнер, вы только скроете ее. Это может быть несколько выгодно, если вы можете использовать только одну (или несколько) универсальных фабрик и, как таковые, уменьшить количество прямых зависимостей контейнеров = меньше работы для изменения при переключении контейнеров. Однако есть те, кто говорит, что общие фабричные интерфейсы нарушают принцип разделения интерфейсов (ISP) ... - person BatteryBackupUnit; 31.10.2014
comment
Да, мне тоже нравятся расширения Ninject Factory. Я не сталкивался с проблемами ISP, мне нужно будет провести небольшое исследование. Ваше здоровье - person Jeremy F; 01.11.2014