Параллельные поставленные в очередь фоновые задачи с размещенными службами в 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>();
}
Вы можете скрыть это в методе расширения и сделать уровень параллелизма настраиваемым, чтобы поддерживать чистоту.