简体   繁体   English

ASP.NET Core中托管服务的并行排队后台任务

[英]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确实关心结果。

  • You do want to make sure a database write went through 您确实希望确保数据库写入完成
  • You do want to make sure a Log entry is written to the hard drive 您确实希望确保将日志条目写入硬盘驱动器
  • You do want to make sure a network packet is sent to the receiver 您确实希望确保将网络数据包发送到接收方

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.

相关问题 具有托管服务的基于ASP.NET Core事件的后台任务 - ASP.NET Core event based background tasks with hosted services ASP.Net Core Queued 后台任务并行处理 - ASP .Net Core Queued background tasks parallel processing C# 处理 IAsyncEnumerable 项目与 asp.net 核心托管服务消费者中的多个并行任务 - C# process IAsyncEnumerable items with several parallel tasks in asp.net core hosted service consumer 防止并行执行扩展的 ASP.NET Core 托管服务 - Prevent parallel executions of scaled ASP.NET Core hosted service 在asp.NET Core MVC网站上启动并行任务 - Launching parallel Tasks on an asp.NET Core MVC web 在 Asp.net Core 中并行运行异步等待任务 - Run async await tasks in parallel in Asp.net Core 在 ASP.NET 核心的托管服务中使用范围服务 - Using Scoped services within a hosted service in ASP.NET Core 在 ASP.NET Core 3.1 中,如何在未来的特定日期和时间安排带有托管服务的后台任务(Cron 作业)? - In ASP.NET Core 3.1, how can I schedule a background task (Cron Jobs) with hosted services for a specific date and time in the future? 任务和并行编程 ASP.Net - Tasks and Parallel Programming ASP.Net 在asp.net核心中排队任务 - Queuing tasks in asp.net core
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM