Параллельные поставленные в очередь фоновые задачи с размещенными службами в ASP.NET Core

Я провожу некоторые тесты с новыми фоновыми задачами с размещенными службами в компоненте ASP.NET Core, представленном в версии 2.1, более конкретно с фоновыми задачами в очереди, и у меня возник вопрос о параллелизме.

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

У меня вопрос, это поведение ожидается? И если это так, чтобы параллельное выполнение запроса выполнялось, можно ли его запустить и забыть, вместо того, чтобы ждать завершения workItem?

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

Изменить: код из учебника является довольно длинным, поэтому ссылка на него https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1

Метод, который выполняет рабочий элемент, таков:

public class QueuedHostedService : IHostedService
{
    ...

    public Task StartAsync(CancellationToken cancellationToken)
    {
        _logger.LogInformation("Queued Hosted Service is starting.");

        _backgroundTask = Task.Run(BackgroundProceessing);

        return Task.CompletedTask;
    }

    private async Task BackgroundProceessing()
    {
        while (!_shutdown.IsCancellationRequested)
        {
            var workItem = 
                await TaskQueue.DequeueAsync(_shutdown.Token);

            try
            {
                await workItem(_shutdown.Token);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, 
                    $"Error occurred executing {nameof(workItem)}.");
            }
        }
    }

    ...
}

Основной вопрос заключается в том, чтобы узнать, может ли кто-нибудь поделиться знаниями о том, как использовать эту конкретную технологию для одновременного выполнения нескольких рабочих элементов, поскольку сервер может справиться с этой рабочей нагрузкой.

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

2 ответа

Решение

Размещенный вами код выполняет поставленные в очередь элементы по порядку, по одному, а также параллельно веб-серверу. IHostedService выполняется по определению параллельно с веб-сервером. Эта статья дает хороший обзор.

Рассмотрим следующий пример:

_logger.LogInformation ("Before()");
for (var i = 0; i < 10; i++)
{
  var j = i;
  _backgroundTaskQueue.QueueBackgroundWorkItem (async token =>
  {
    var random = new Random();
    await Task.Delay (random.Next (50, 1000), token);
    _logger.LogInformation ($"Event {j}");
  });
}
_logger.LogInformation ("After()");

Мы добавили десять задач, которые будут ждать случайное количество времени. Если вы поместите код в метод контроллера, события все равно будут регистрироваться даже после возврата метода контроллера. Но каждый элемент будет выполнен по порядку, так что вывод будет выглядеть так:

Event 1
Event 2
...
Event 9
Event 10

Чтобы ввести параллелизм, мы должны изменить реализацию BackgroundProceessing метод в QueuedHostedService,


Вот пример реализации, которая позволяет параллельно выполнять две задачи:

private async Task BackgroundProceessing()
{
  var semaphore = new SemaphoreSlim (2);

  void HandleTask(Task task)
  {
    semaphore.Release();
  }

  while (!_shutdown.IsCancellationRequested)
  {
    await semaphore.WaitAsync();
    var item = await TaskQueue.DequeueAsync(_shutdown.Token);

    var task = item (_shutdown.Token);
    task.ContinueWith (HandleTask);
  }
}

При использовании этой реализации порядок событий, вошедших в систему, больше не соответствует порядку, поскольку каждая задача ожидает случайное количество времени. Таким образом, вывод может быть:

Event 0
Event 1
Event 2
Event 3
Event 4
Event 5
Event 7
Event 6
Event 9
Event 8

редактировать: нормально ли в производственной среде выполнять код таким образом, не ожидая его?

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

Когда вы выполняете Task используя fire-and-забудьте, вы в основном говорите мне, что вас не волнует результат этой функции. Вам все равно, если он успешно завершен, отменен или вызвал исключение. Но для большинства Task s вы заботитесь о результате.

  • Вы хотите убедиться, что запись в базу данных прошла
  • Вы хотите убедиться, что запись журнала записана на жесткий диск
  • Вы хотите убедиться, что сетевой пакет отправлен получателю

И если вы заботитесь о результате Task тогда огонь и забыл - неправильный метод.

Вот и все на мой взгляд. Трудная часть заключается в поиске Task где вы действительно не заботитесь о результате Task,

Вы можете добавить QueuedHostedService один или два раза для каждого процессора в машине.

Так что-то вроде этого:

for (var i=0;i<Environment.ProcessorCount;++i)
{
    services.AddHostedService<QueuedHostedService>();
}

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

Другие вопросы по тегам