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

时间:2018-07-15 15:39:38

标签: c# parallel-processing asp.net-core-2.1 asp.net-core-hosted-services

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

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

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

我已经搜索了几天没有这种运气的特定情况,因此,如果有人可以提供任何指南或示例,我将非常高兴。

编辑:本教程中的代码很长,因此其链接为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)}.");
            }
        }
    }

    ...
}

这个问题的重点是要知道那里是否有人可以共享有关如何使用此特定技术来同时执行多个工作项的知识,因为服务器可以处理此工作工作量。

我在执行工作项时尝试了一种“即发即弃”方法,并且按我预期的方式工作,同时并行执行了多个任务,我不确定这是否可行,还是有更好或适当的方式来处理这种情况。

2 个答案:

答案 0 :(得分:4)

您发布的代码依次执行,一次一项,但与网络服务器并行的排队的项目。 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()");

我们添加了十个任务,这些任务将等待随机的时间。如果将代码放在控制器方法中,即使控制器方法返回后,事件仍将记录下来。但是每个项目将按顺序执行,以便输出如下所示:

Event 1
Event 2
...
Event 9
Event 10

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


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

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

edit:在生产环境中以这种方式执行代码而无需等待就可以了吗?

我认为大多数开发人员在“一劳永逸”问题上的原因是它经常被滥用。

当您使用即发即弃方式执行Task时,基本上是在告诉我您不关心此函数的结果。您不必担心它是否成功退出,是否已取消或是否引发了异常。但是对于大多数Task,您愿意关心结果。

  • 您确实要确保对数据库进行写操作
  • 您确实要确保将日志条目写入硬盘驱动器
  • 您确实要确保将网络数据包发送到接收器

如果您关心Task的结果,那么“即发即弃”是错误的方法

我认为就是这样。困难的部分是找到一个Task,而您实际上并不关心Task的结果。

答案 1 :(得分:3)

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

是这样的:

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

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