对于事件驱动的后台任务,从 ExecuteAsync 返回什么?



作为我的REST API的一部分,我有一个BackgroundService,它订阅消息队列并等待接收特定消息,并根据消息类型执行某些操作。

在 ExecuteAsync 中,我目前正在返回 Task.CompleteTask,因为我没有什么可以"等待"的,但是我认为这实际上是不正确的,并且可能存在潜在问题,尽管我不确定究竟是什么或什么问题可能出现结果。

简单地"等待"任务延迟无限延迟是否可以接受?像这样:

await Task.Delay(Timeout.infinite, cancellationToken);

TLDR

从本质上讲,它归结为: 返回Task.CompleteTask.

我什至建议直接使用IHostedService而不是BackgroundService,因为您并没有真正从我所看到的中获得任何东西。

我服务机构

首先要研究的是IHostedService.此接口是BackgroundService实现的接口,也是在主机启动时启用运行代码的接口。该接口仅添加StartAsyncStopAsync方法(无ExecuteAsync方法)。

如果我们查看Microsoft.Extensions.Hosting的源代码,我们可以看到,当我们调用AddHostedService时,它所做的只是将IHostedService添加到IServiceProvider

public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services)
where THostedService : class, IHostedService
{
services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>());
return services;
}

然后,在Host.StartAsync里面,它做一些启动的事情,但主要是它如何启动IHostedService

public async Task StartAsync(CancellationToken cancellationToken = default)
{
// Omitted for brevity 
_hostedServices = Services.GetService<IEnumerable<IHostedService>>();
foreach (IHostedService hostedService in _hostedServices)
{
// Fire IHostedService.Start
await hostedService.StartAsync(combinedCancellationToken).ConfigureAwait(false);
if (hostedService is BackgroundService backgroundService)
{
_ = HandleBackgroundException(backgroundService);
}
}
// Omitted for brevity 
}

为什么要显示这一切?我认为重要的是要知道这里没有真正的魔术发生。每当调用主机上的StartAsync时,它所做的只是在 IHostedServices 上调用StartAsync。相反,仅在调用Host.StopAsync时调用IHostedService上的StopAsync

那么是什么让BackgroundService与众不同呢?

后台服务

您可能已经注意到,在启动IHostedService时有一些特殊的代码,请检查它是否是BackgroundService。您已经发布了指向BackgroundService源的链接,但我将在此处发布主要部分以提高可见性。

public virtual Task ExecuteTask => _executeTask;
protected abstract Task ExecuteAsync(CancellationToken stoppingToken);
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Create linked token to allow cancelling executing task from provided token
_stoppingCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
// Store the task we're executing
_executeTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it, this will bubble cancellation and failure to the caller
if (_executeTask.IsCompleted)
{
return _executeTask;
}
// Otherwise it's running
return Task.CompletedTask;
}

如您所见,它实际上从未等待从ExecuteAsync返回的任务。这就是使我们能够在BackgroundService内进行任务的原因。如果您尝试在正常IHostedService内进行具有巨大延迟的Task.Delay,您的主机将永远无法正常启动。

在您从ExecuteAsync返回Task.CompleteTask的情况下,它只会将完成的任务从Host返回到StartAsync

那么用BackgroundTask做这一切有什么意义呢?如果我错了,有人纠正我,但从外观上看,它似乎只存在于长时间运行的任务上的异常处理中。如果我们看一下Host.StartAsync内部的HandleBackgroundException,我们会看到:

private async Task HandleBackgroundException(BackgroundService backgroundService)
{
try
{
await backgroundService.ExecuteTask.ConfigureAwait(false);
}
catch (Exception ex)
{
_logger.BackgroundServiceFaulted(ex);
}
}

它所做的只是从ExecuteAsync中获取任务并等待它,这使我们能够通过将其发送到记录器来查看抛出的任何异常。

结论

根据我在源代码中看到的内容,我没有看到任何关于您的案件的内容值得BackgroundService.

您说过">它不是一个异步 API,因此不需要等待",这对我来说意味着您不会从BackgroundService中获得任何好处。

您应该只使用IHostedService并在StartAsync中设置回调,然后在StopAsync中删除它们。据我所知,没有理由这行不通。这不像你的IHostedService被停止,因为它不再"运行"。

如果您想坚持使用后台服务,那么返回Task.CompleteTask而不是等待延迟会更明智。延迟仍将使用一些资源来保留计时器,即使这很小。

最新更新