首页 > 解决方案 > 对于事件驱动的后台任务,从 ExecuteAsync 返回什么?

问题描述

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

在 ExecuteAsync 中,我目前正在返回 Task.CompletedTask,因为我没有任何要“等待”的东西,但是我认为这实际上是不正确的,并且可能存在潜在问题,尽管我不确定究竟会出现什么问题或出现什么问题因此。

以无限延迟简单地“等待”Task.Delay 是否可以接受?就像是:

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

标签: c#asp.net-core.net-core

解决方案


TLDR

本质上它归结为: Return Task.CompleteTask

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

托管服务

首先要研究的是IHostedService。这个接口是BackgroundService实现的,并且是在主机启动时允许运行代码的接口。接口只添加了StartAsyncandStopAsync方法(没有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's.

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调用主机时,它所做的只是调用StartAsyncIHostedServices。反之StopAsyncIHostedService仅在被调用时才Host.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. 如果您尝试在Task.Delaynormal 中以巨大的延迟执行 a IHostedService,您的 Host 将永远无法正常启动。

在您返回Task.CompleteTaskfrom的情况下ExecuteAsync,它只会将完成的任务返回StartAsyncfrom Host

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

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

它所做的一切都是从任务中获取ExecuteAsync并等待它,这使我们能够看到通过将其发送到 Logger 所引发的任何异常。

结论

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

你说过'它不是一个异步 API,因此不需要等待',这对我来说意味着你没有从BackgroundService.

您应该只使用 aIHostedService并在 中设置回调StartAsync,然后在 中删除它们StopAsync。我没有理由看到这不起作用。这不像你IHostedService被停止,因为它不再“运行”了。

如果您想坚持使用 BackgroundService,返回Task.CompleteTask而不是等待延迟会更明智。延迟仍然会使用一些资源来保持计时器,即使它很小。


推荐阅读