15

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

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

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

我已经搜索了几天关于这个特定场景的运气,所以如果有人提供任何指南或示例,我会非常高兴。

编辑:教程中的代码很长,所以它的链接是https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-2.1#queued -后台任务

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

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

    ...
}

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

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

4

2 回答 2

11

您发布的代码按顺序执行排队的项目,一次一个,但也与 Web 服务器并行。AnIHostedService正在按照定义与 Web 服务器并行运行。本文提供了一个很好的概述。

考虑以下示例:

_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

编辑:在生产环境中以这种方式执行代码是否可以,无需等待?

我认为大多数开发人员对即发即弃存在问题的原因是它经常被滥用。

当您执行Taskusing 即发即弃时,您基本上是在告诉我您不关心此函数的结果。您不关心它是否成功退出、是否被取消或是否引发异常。但对于大多数人来说Task,你确实关心结果。

  • 您确实想确保数据库写入通过
  • 您确实要确保将日志条目写入硬盘驱动器
  • 您确实要确保将网络数据包发送到接收方

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

在我看来就是这样。困难的部分是找到一个Task你真的不关心结果的地方Task

于 2018-07-15T16:51:52.390 回答
3

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

所以是这样的:

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

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

于 2018-11-22T22:15:23.563 回答