Фоновое задание Hangfire выдает сообщение «Недостаточно памяти для продолжения выполнения программы».

#c# #multithreading #asp.net-core #hangfire #parallel.foreach

#c# #многопоточность #asp.net-core #hangfire #parallel.foreach

Вопрос:

мне трудно разобраться с этим, может быть, кто-нибудь сможет мне здесь помочь.

у меня есть Asp.net Веб-приложение Core 2.0, я использую Hangfire для обработки заданий в фоновом режиме, обычно для синхронизации продуктов или заказов между различными платформами, но есть одно задание, которое всегда завершается неудачей с «Недостаточно памяти для продолжения выполнения программы». а также «Ссылка на объект не установлена для экземпляра объекта» я регистрирую трассировку, и она переводит меня в параллель.Везде, где выдается ошибка, теперь, когда я делаю то же самое вручную (имеется в виду без hangfire), я не получаю исключений, и все работает просто отлично.

итак, позвольте мне поделиться здесь некоторым кодом.

итак, сначала мы настраиваем hangfire следующим образом. внутри класса startup, в методе Configure, мы делаем это.

             app.UseHangfireDashboard("/tasks", new DashboardOptions() {
            Authorization = new[] { new HangFireAuthorizationFilter() }
        });

            var schedules = new ScheduleTasks(app.ApplicationServices);
            schedules.RegisterRecurentJobs();
  

теперь ScheduleTasks выглядит следующим образом.

     public class ScheduleTasks
{
    private readonly IApiService _apiService;
    private readonly ISettingService _settingService;

    public ScheduleTasks(IServiceProvider serviceProvider)
    {
        var scope = serviceProvider.CreateScope();
        _apiService = scope.ServiceProvider.GetService<IApiService>();
        _settingService = scope.ServiceProvider.GetService<ISettingService>();
    }

    public void RegisterRecurentJobs()
    {
        var settings = _settingService.LoadSetting<ApiSettings>();

        if(!settings.SilentMode)
        {
            RecurringJob.AddOrUpdate<IApiService>("Config", x => x.SyncConfig(), "* 1 * * *");
            RecurringJob.AddOrUpdate<IApiService>("Manufacturers", x => x.SyncManufacturers(), "* 2 * * *");
            RecurringJob.AddOrUpdate<IApiService>("Products", x => x.SyncProducts(30, false), "* 3 * * *");
        }

    }
}
  

и теперь вот мой метод api service, который выполнил SyncProducts.

         public async Task SyncProducts(int days, bool hasSalesOnly = true)
    {

        var s = new StringBuilder();
        try
        {
            var search = new SearchModel()
            {
                ModifiedDate = new DateRange()
                {
                    FromDate = DateTime.Today.AddDays(-days)
                },
                PageSize = 300000,
                HaveSalesOnly = hasSalesOnly
            };

            var ids = await _productService.GetProductFeed(search);
            var hasNext = ids?.Result?.AdditionalResultsId;
            var failedIds = new List<string>();
            var retry = true;

            while (ids.Result.Ids != null amp;amp; ids.Result.Ids.Count > 0)
            {
                var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = 20 };

                s.Append("[");
                var start = true;

                Parallel.ForEach(ids.Result.Ids, parallelOptions, id =>
                {
                    var itemResult = _productService.GetProductByProductId(id).Resu<
                    if (itemResult.Success)
                    {
                        lock (s)
                        {
                            if (start)
                            {
                                s.Append(itemResult.ResponseString);
                            }
                            else
                            {
                                var r = ","   itemResult.ResponseString;
                                s.Append(r);
                            }

                            start = false;
                        }
                    }
                    else
                    {
                        failedIds.Add(id);
                    }

                    itemResult = null;

                });

                s.Append("]");

                var newFileName = "result.json";
                var newFileDir = GetJsonFileDirectoryPath()   newFileName;
                var baseDir = Environment.CurrentDirectory   "/wwwroot";
                var dataPath = baseDir   newFileDir;
                File.WriteAllText(dataPath, s.ToString());
                s = null;

                ids.Result.Ids = null;

                var param = new SqlParameter
                {
                    ParameterName = "path",
                    SqlDbType = SqlDbType.VarChar,
                    Direction = ParameterDirection.Input,
                    Value = dataPath
                };

                var imagesToProcess = await _itemRepository.ExecuteStoredProcedureList<SyncItems>("[dbo].[SyncItems]", param);

                foreach (var image in imagesToProcess)
                {
                    if (image.ProductId == null)
                        continue;

                    //for now delete all images, and save the new ones.
                    var imagesToDelete = _pictureService.GetProductPicturesByProductId(image.ProductId.Value);

                    foreach (var i in imagesToDelete)
                    {
                        await _pictureService.DeleteProductPicture(i);
                    }

                    if (image.ImageUrls != null)
                    {
                        var imageList = image.ImageUrls.Split(',');

                        foreach (var i in imageList)
                        {
                            await _pictureService.InsertProductPictureFromPictureUrl(i, image.ProductId.Value);
                        }
                    }


                }

                if (!string.IsNullOrEmpty(hasNext))
                {
                    ids = await _productService.GetNextPage(ids.Result.AdditionalResultsId);
                    hasNext = ids.Result.AdditionalResultsId;
                }
                else
                {
                    ids.Result.Ids = null;
                }

                if (string.IsNullOrEmpty(hasNext) amp;amp; ids.Result.Ids == null amp;amp; retry ||
                    string.IsNullOrEmpty(hasNext) amp;amp; ids.Result.Ids.Count == 0 amp;amp; retry)
                {
                    ids = new BaseSearchResult();
                    ids.Result.Ids = failedIds;

                    failedIds = new List<string>();
                    retry = false;
                }
            }

            ids = null;

        }
        catch (Exception ex)
        {
            s = null;
            aEvent = new ApiEvent()
            {
                Level = LogLevel.Critical,
                EventDate = DateTime.Now,
                User = "Api",
                EventType = EventTypeEnum.Product,
                EntityTypeId = "",
                Event = ex.Message,
                Details = ex.StackTrace
            };

            await _eventService.PublishEvent(aEvent);
        }

    }
  

если я выполняю этот метод вручную, либо путем написания функционального теста для выполнения метода, либо с помощью кнопки на моей странице, которая запускает выполнение этого метода, все работает нормально, но когда Hangfire пытается выполнить точно такой же метод, я получаю эту ошибку.

Произошла одна или несколько ошибок. (Недостаточно памяти для продолжения выполнения программы.) (Ссылка на объект не установлена на экземпляр объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.) (Ссылка на объект не установлена для экземпляра объекта.)

и трассировка такая.

в системе.Многопоточность.Задачи.TaskReplicator.Запустите[TState] (тело ReplicatableUserAction 1 action, ParallelOptions options, Boolean stopOnFirstFailure)
at System.Threading.Tasks.Parallel.ForWorker[TLocal](Int32 fromInclusive, Int32 toExclusive, ParallelOptions parallelOptions, Action
1, действие 2 bodyWithState, Func 4 bodyWithLocal, функция 1 localInit, Action 1 localFinally) — Завершение трассировки стека из предыдущего местоположения, где было сгенерировано исключение — в системе.Многопоточность.Задачи.Параллельно.Вызывает в системе исключение ICollection, CancellationToken cancelToken, исключение otherException).Многопоточность.Задачи.Параллельно.ForWorker[TLocal](Int32 из исключения, Int32 в исключение, параллельные опции ParallelOptions, Действие 1 body, Action 2 bodyWithState, Функция 4 bodyWithLocal, Func 1 localInit, Действие 1 localFinally)
at System.Threading.Tasks.Parallel.ForEachWorker[TSource,TLocal](IList
1 list, Параллельные опции ParallelOptions, Действие 1 body, Action 2 bodyWithState, Действие 3 bodyWithStateAndIndex, Func 4 BODYWITHSTATE andlocal, Функция 5 bodyWithEverything, Func 1 localInit, Действие 1 localFinally)
at System.Threading.Tasks.Parallel.ForEachWorker[TSource,TLocal](IEnumerable
1 source, Параллельные опции ParallelOptions, Действие 1 body, Action 2 bodyWithState, Действие 3 bodyWithStateAndIndex, Func 4 bodyWithStateAndLocal, Функция 5 bodyWithEverything, Func 1 localInit, Действие 1 localFinally)
at System.Threading.Tasks.Parallel.ForEach[TSource](IEnumerable
1 source, параллельные опции ParallelOptions, Действие`1 body) в *****.*****. Службы.ApiService.SyncProducts (Int32 дня, логическое значение имеет значение только) в C:***************.**** Services ApiService.cs: строка 493

кто-нибудь может помочь мне понять, почему происходит сбой, когда hangfire выполняет метод?

заранее спасибо всем, кто изучает это.

Комментарии:

1. Кровавое убийство, это какой-то плохой код. Я не уверен, в чем именно заключается ваша проблема, поскольку это может быть любое количество проблем. Вы загружаете в память кучу дерьмовых объектов. Вы блокируете асинхронность. У вас чрезвычайно неэффективные запросы к базе данных, и при этом их очень много. У вас есть фактические блокировки. Вы выполняете наивный и неэффективный доступ к файлам. Список можно продолжить.

2. @ChrisPratt причина, по которой я загружаю их в память, заключается в том, что используемый мной api не поддерживает плоские файлы или что-либо подобное, поэтому я могу получить только 1 продукт за раз, поэтому мой лучший способ сделать это быстро — сначала получить список идентификаторов, которые мне нужно обновить, а затем поместить их в параллельный многопоточный, чтобы получить информацию о каждом продукте в списке, таким образом, это делается довольно быстро.

3. @ChrisPratt я не выполняю никаких запросов к базе данных до момента обработки изображений, я использую хранимую процедуру для обработки всего массива данных, которые я записываю в файл json, все службы, которые вы видите, — это вызовы api, которые мне нужно выполнить на основе документации api, которую я получил от этой третьей стороны.

4. @ChrisPratt причина, по которой я устанавливаю блокировку, заключается в том, что string builder не является многопоточным, и поскольку мне нужен доступ к string builder в многопоточном режиме, мне нужно добавить к нему блокировку.

5. Используйте SemaphoreSlim вместо этого.