[英]Parallel queued background tasks with hosted services in ASP.NET Core
I'm doing some tests with the new Background tasks with hosted services in ASP.NET Core feature present in version 2.1, more specifically with Queued background tasks, and a question about parallelism came to my mind. 我正在使用版本2.1中存在的ASP.NET核心功能中的托管服务的新后台任务进行一些测试,更具体地说是使用排队后台任务,并且我想到了关于并行性的问题。
I'm currently following strictly the tutorial provided by Microsoft and when trying to simulate a workload with several requests being made from a same user to enqueue tasks I noticed that all workItems are executed in order, so no parallelism. 我目前正在严格遵循Microsoft提供的教程,当尝试模拟工作负载时,从同一个用户发出多个请求以排队任务我注意到所有workItem都按顺序执行,因此没有并行性。
My question is, is this behavior expected? 我的问题是,这种行为是否有望? And if so, in order to make the request execution parallel is it ok to fire and forget, instead of waiting the workItem to complete? 如果是这样,为了使请求执行并行,可以解雇并忘记,而不是等待workItem完成?
I've searched for a couple of days about this specific scenario without luck, so if anyone has any guide or examples to provide, I would be really glad. 我已经搜索了几天没有运气的特定情况,所以如果有人提供任何指南或示例,我会很高兴。
Edit: The code from the tutorial is quite long, so the link for it is https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1#queued-background-tasks 编辑:教程中的代码很长,因此它的链接是https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1#queued -background任务
The method which executes the work item is this: 执行工作项的方法是这样的:
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)}.");
}
}
}
...
}
The main point of the question is to know if anyone out there could share the knowledge of how to use this specific technology to execute several work items at the same time, since a server can handle this workload. 问题的关键是要知道是否有人可以分享如何使用这种特定技术同时执行多个工作项的知识,因为服务器可以处理这种工作量。
I tried the fire and forget method when executing the work item and it worked the way I intended it to, several tasks executing in parallel at the same time, I 'm jut no sure if this is an ok practice, or if there is a better or proper way of handling this situation. 我在执行工作项时尝试了火灾和忘记方法,它按照我的预期方式工作,同时并行执行的几个任务,我不确定这是否是一个好的做法,或者是否有一个更好或更好地处理这种情况的方法。
The code you posted executes the queued items in order , one at a time but also in parallel to the web server. 您发布的代码按顺序执行排队项目, 一次一个,但也与Web服务器并行执行。 An IHostedService
is running per definition in parallel to the web server. IHostedService
与Web服务器并行运行。 This article provides a good overview. 该文章提供了一个很好的概述。
Consider the following example: 请考虑以下示例:
_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()");
We add ten tasks which will wait a random amount of time. 我们添加了十个任务,这些任务将等待一段随机时间。 If you put the code in a controller method the events will still be logged even after controller method returns. 如果将代码放在控制器方法中,即使在控制器方法返回后,仍会记录事件。 But each item will be executed in order so that the output looks like this: 但是每个项目都将按顺序执行,以便输出如下所示:
Event 1
Event 2
...
Event 9
Event 10
In order to introduce parallelism we have to change the implementation of the BackgroundProceessing
method in the QueuedHostedService
. 为了引入并行性,我们必须在QueuedHostedService
更改BackgroundProceessing
方法的QueuedHostedService
。
Here is an example implementation that allows two Tasks to be executed in parallel: 这是一个允许两个任务并行执行的示例实现:
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);
}
}
Using this implementation the order of the events logged in no longer in order as each task waits a random amount of time. 使用此实现时,由于每个任务等待一段随机时间,因此登录的事件的顺序不再按顺序排列。 So the output could be: 所以输出可能是:
Event 0
Event 1
Event 2
Event 3
Event 4
Event 5
Event 7
Event 6
Event 9
Event 8
edit: Is it ok in a production environment to execute code this way, without awaiting it? 编辑: 在生产环境中以这种方式执行代码是否可以,无需等待它?
I think the reason why most devs have a problem with fire-and-forget is that it is often misused. 我认为大多数开发人员遇到“一劳永逸”问题的原因是它经常被滥用。
When you execute a Task
using fire-and-forget you are basically telling me that you do not care about the result of this function. 当你使用fire-and-forget执行一个Task
,你基本上告诉我你不关心这个函数的结果。 You do not care if it exits successfully, if it is canceled or if it threw an exception. 你不在乎它是否成功退出,是否被取消或是否引发了异常。 But for most Task
s you do care about the result. 但对于大多数Task
你确实关心结果。
And if you care about the result of the Task
then fire-and-forget is the wrong method . 如果你关心Task
的结果,那么“即发即忘”是错误的方法 。
That's it in my opinion. 在我看来,这就是它。 The hard part is finding a Task
where you really do not care about the result of the Task
. 困难的部分是找到一个Task
,你真的不关心Task
的结果。
You can add the QueuedHostedService once or twice for every CPU in the machine. 您可以为计算机中的每个CPU添加一次或两次QueuedHostedService。
So something like this: 所以像这样:
for (var i=0;i<Environment.ProcessorCount;++i)
{
services.AddHostedService<QueuedHostedService>();
}
You can hide this in an extension method and make the concurrency level configurable to keep things clean. 您可以在扩展方法中隐藏它,并使并发级别可配置以保持干净。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.