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

Parallel queued background tasks with hosted services in ASP.NET Core

我正在使用 ASP.NET 版本 2.1 中的核心功能中的新后台任务和托管服务进行一些测试,更具体地说是队列后台任务,我想到了一个关于并行性的问题。

我目前严格按照 Microsoft 提供的教程进行操作,在尝试模拟工作负载时,同一用户发出多个请求以将任务排队,我注意到所有工作项都是按顺序执行的,因此没有并行性。

我的问题是,这种行为是否符合预期?如果是这样,为了使请求并行执行,可以先发后忘,而不是等待 workItem 完成吗?

我已经针对这个特定场景搜索了几天,但没有成功,所以如果有人能提供任何指南或示例,我将非常高兴。

编辑:教程中的代码很长,所以 link 是 https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1#queued-background-tasks

执行工作项的方法是这样的:

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)}.");
            }
        }
    }

    ...
}

问题的重点是想知道是否有人可以分享如何使用此特定技术同时执行多个工作项目的知识,因为服务器可以处理此工作负载。

我在执行工作项时尝试了即发即忘的方法,它按照我预期的方式工作,多个任务同时并行执行,我只是不确定这是否是一种好的做法,或者是否有更好或正确的方法来处理这种情况。

您发布的代码执行排队的项目按顺序一次一个,但也并行 到网络服务器。 IHostedService 是 运行 每个与 Web 服务器并行的定义。 This 文章提供了很好的概述。

考虑以下示例:

_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()");

我们添加了 10 个随机等待时间的任务。如果将代码放在控制器方法中,即使在控制器方法 returns 之后,事件仍将被记录。但是每一项都会按顺序执行,所以输出看起来像这样:

Event 1
Event 2
...
Event 9
Event 10

为了引入并行性,我们必须更改 QueuedHostedServiceBackgroundProceessing 方法的实现。


这是一个允许并行执行两个任务的示例实现:

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 时,您基本上是在告诉我您不关心此函数的结果。您不关心它是否成功退出、是否被取消或是否抛出异常。但是对于大多数 Task 来说,您 确实 关心结果。

  • 您确实想确保数据库写入已经完成
  • 您确实想确保将日志条目写入硬盘
  • 您确实想确保网络数据包已发送到接收方

如果您关心 Task 的结果,那么一劳永逸就是 错误的方法

我认为就是这样。困难的部分是找到一个 Task 你真的不关心 Task.

的结果

您可以为机器中的每个 CPU 添加一次或两次 QueuedHostedService。

所以像这样:

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

您可以将其隐藏在扩展方法中,并使并发级别可配置以保持整洁。