IHostedService 的多种实现

Posted

技术标签:

【中文标题】IHostedService 的多种实现【英文标题】:Multiple Implementations of IHostedService 【发布时间】:2018-10-09 02:05:47 【问题描述】:

我正在尝试使用 IHostedService 创建后台服务。如果我只有一个后台服务,一切正常。当我尝试创建多个IHostedService 的实现时,只有第一个注册的实际运行。

services.AddSingleton<IHostedService, HostedServiceOne>();
services.AddSingleton<IHostedService, HostedServiceTwo>();

在上面的示例中,HostedServiceOne 上的 StartAsync 被调用,但 HostedServiceTwo 上的 StartAsync 永远不会被调用。如果我交换注册IHostedService 的两个实现的顺序(将IHostedServiceTwo 放在IHostedServiceOne 之前),那么HostedServiceTwo 上的StartAsync 会被调用,但不会被HostedServiceOne 调用。

编辑:

我被定向到以下内容:

How to register multiple implementations of the same interface in Asp.Net Core?

但这不适用于IHostedService。要使用建议的方法,我必须调用serviceProvider.GetServices&lt;IService&gt;();,但似乎IHostedService.StartAsync 似乎是在内部调用的。我什至不确定我会在哪里调用它来触发IHostedService.StartAsync

【问题讨论】:

提供的解决方案似乎需要调用serviceProvider.GetServices。我会在哪里调用它来触发 IHostedService.StartAsync。使用单个 IHostedService 时,似乎在内部调用了 StartAsync 方法。 我明白了,那不是重复的,我一会儿回答这个问题 【参考方案1】:

我遇到了同样的问题。 每个服务都需要返回Task.CompletedTask;

public class MyHostedService: IHostedService

    public Task StartAsync(CancellationToken cancellationToken)
    
        Task.Run(() => SomeInfinityProcess(cancellationToken));
        return Task.CompletedTask;
    

    public void SomeInfinityProcess(CancellationToken cancellationToken)
    
        for (; ; )
        
            Thread.Sleep(1000);
            if (cancellationToken.IsCancellationRequested)
                break;
        
    

    public Task StopAsync(CancellationToken cancellationToken)
    
        return Task.CompletedTask;
    

Startup.cs 相同:

    services.AddHostedService<MyHostedService>();
    services.AddHostedService<MyHostedService2>();
    ...

【讨论】:

【参考方案2】:

注册您的HostedService如下:

// services.AddSingleton<IHostedService, HostedServiceOne>();
// services.AddSingleton<IHostedService, HostedServiceTwo>();

services.AddHostedService<HostedServiceOne>();
services.AddHostedService<HostedServiceTwo>();

[更新]:

请参阅@nickvane 下面的 cmets:

这是因为第一个注册的服务没有在 StartAsync 方法上返回任务,所以运行时正在等待并且不执行下一个注册的托管服务实例的 StartAsync

可能第一个 StartAsync() 没有完成

【讨论】:

AddHostedService的实现只有1行:services.AddTransient&lt;IHostedService, THostedService&gt;(); @nickvane 那么你的意思是我们需要注册一个临时服务器而不是使用AddHostedService 吗? 我的意思是,除了瞬态与单例范围之外,代码是相同的,并且范围更改不是问题的原因。您可以将其注册为瞬态或单例,但这不能解决问题。这是因为注册的第一个服务没有在 StartAsync 方法上返回 Task,所以运行时正在等待并且不执行下一个注册的托管服务实例的 StartAsync。 @nickvane 听起来很有希望。我按照@Harry 答案中的链接找到It's waiting for the first one to finish starting before it tries to start the next one。我没有尝试让第一个StartAsync 不返回任务。我认为如果我返回其他内容,它将无法编译。但是如果第一个 StartAsync 没有完成,那是有道理的。虽然 OP 没有向我们展示代码,但我想你是对的,应该是这样的。【参考方案3】:

保持异步!正如这里所讨论的......

https://github.com/aspnet/Hosting/issues/1560

protected async override Task ExecuteAsync(CancellationToken stoppingToken)
            
    while (!stoppingToken.IsCancellationRequested)
    
        // Do something nice here
        await Task.Delay(2000);
    
   

【讨论】:

我跑了 Thread.Sleep(1000) 并且摸不着头脑,为什么我的其他工人没有执行... 这应该是答案,但@Harry 没有提到您的托管服务类必须继承 BackgroundService。 - 可在 Microsoft.Extensions.Hosting 中找到【参考方案4】:

这似乎可以通过decoratingIHostedService 解决,虽然.Net Core 的默认IoC 容器不支持注册装饰器,但有一个简单的解决方法。

您可以像这样为IHostedService 创建一个装饰器:

public abstract class MyHostedServiceDecorator : IHostedService

    private readonly MyHostedServiceDecorator _next;

    protected MyHostedServiceDecorator(MyHostedServiceDecorator next)
    
        _next = next;
    

    public async Task StartAsync(CancellationToken cancellationToken)
    
        await StartAsyncInternal(cancellationToken);

        if (_next != null)
        
            await _next.StartAsync(cancellationToken);
        
    

    public async Task StopAsync(CancellationToken cancellationToken)
    
        await StopAsyncInternal(cancellationToken);

        if (_next != null)
        
            await _next.StopAsync(cancellationToken);
        
    

    protected abstract Task StartAsyncInternal(CancellationToken token);

    protected abstract Task StopAsyncInternal(CancellationToken token);

创建尽可能多的装饰器,例如:

public class HostedServiceOneDecorator : MyHostedServiceDecorator

    public HostedServiceOneDecorator(MyHostedServiceDecorator next) : base(next)
    
    

    protected override async Task StartAsyncInternal(CancellationToken token)
    
        Console.Write("This is my decorated start async!");
    

    protected override async Task StopAsyncInternal(CancellationToken token)
    
        Console.Write("This is my decorated stop async!");
    

在您注册的托管服务中,像这样调用装饰器:

public class MyHostedService : IHostedService

    private readonly MyHostedServiceDecorator
        _decorator;

    public MyHostedService(MyHostedServiceDecorator decorator)
    
        _decorator = decorator;
    

    public async Task StartAsync(CancellationToken cancellationToken)
    
        // base StartAsync logic ...
        await _decorator.StartAsync(cancellationToken);
    

    public async Task StopAsync(CancellationToken cancellationToken)
    
        // base StopAsync logic ...
        await _decorator.StopAsync(cancellationToken);
    

最后,您通过在前一个构造函数中传递下一个装饰器来注册服务及其装饰器。

services.AddSingleton<IHostedService, MyHostedService>();

services.AddSingleton<MyHostedServiceDecorator>(
    new HostedServiceOneDecorator(new HostedServiceTwoDecorator(/*etc*/)));

所有的装饰器都会被链式调用,直到没有下一个!

【讨论】:

【参考方案5】:

我遇到了同样的问题,不会调用 IHostedService 的多个实现,只会调用第一个。检查您是否碰巧在 IWebHostBuilder.ConfigureServices() 方法中使用 TryAdd 方法,而不是 Add 方法。第一个不允许接口的重复实现,第二个允许。这为我解决了问题。

所以用这个:

webHost = WebHost
            .CreateDefaultBuilder()
            .ConfigureServices(x => x.Add(serviceCollection))

而不是这个:

webHost = WebHost
            .CreateDefaultBuilder()
            .ConfigureServices(x => x.TryAdd(serviceCollection))

【讨论】:

【参考方案6】:

我遇到了一个稍微不同的问题,但它的解决方案也可能适用于这里。我需要将服务实现注册为单例并让它以IHostedService 运行。基于哈维尔卡佩罗的解决方案,我想出了这样的事情:

public class HostedServiceDecorator<T> : IHostedService where T : IHostedService

    private readonly IHostedService _next;

    public HostedServiceDecorator(T target)
    
        _next = target;
    

    public async Task StartAsync(CancellationToken cancellationToken)
    
        if (_next != null)
        
            await _next.StartAsync(cancellationToken);
        
    

    public async Task StopAsync(CancellationToken cancellationToken)
    
        if (_next != null)
        
            await _next.StopAsync(cancellationToken);
        
    

然后我可以做我需要的:

        services.AddSingleton<INetworkStatusService, NetworkStatusService>();
        services.AddHostedService<HostedServiceDecorator<INetworkStatusService>>();

要回答这个问题,可以这样做:

        services.AddTransient<NetworkStatusService>();
        services.AddHostedService<HostedServiceDecorator<NetworkStatusService>>();

【讨论】:

【参考方案7】:

简短:将IHostedService 切换为BackgroundService

您必须在StartAsync 中结束执行。如果你有一个持久的任务,最好切换到BackgroundService(或打开另一个线程)。 BackgroundService 只有一个 ExecuteAsync 方法(不需要结束)。与IHostedServices StartAsync 方法相比(应该结束,否则其他服务将不会执行)。

public class GracePeriodManagerService : BackgroundService

    private readonly ILogger<GracePeriodManagerService> _logger;
    private readonly OrderingBackgroundSettings _settings;
    private readonly IEventBus _eventBus;

    public GracePeriodManagerService(IOptions<OrderingBackgroundSettings> settings,
                                     IEventBus eventBus,
                                     ILogger<GracePeriodManagerService> logger)
    
        // Constructor's parameters validations...
    

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    
        _logger.LogDebug($"GracePeriodManagerService is starting.");

        stoppingToken.Register(() =>
            _logger.LogDebug($" GracePeriod background task is stopping."));

        while (!stoppingToken.IsCancellationRequested)
        
            _logger.LogDebug($"GracePeriod task doing background work.");

            // This eShopOnContainers method is querying a database table
            // and publishing events into the Event Bus (RabbitMQ / ServiceBus)
            CheckConfirmedGracePeriodOrders();

            await Task.Delay(_settings.CheckUpdateTime, stoppingToken);
        

        _logger.LogDebug($"GracePeriod background task is stopping.");
    

注册工作相同:

public IServiceProvider ConfigureServices(IServiceCollection services)

    //Other DI registrations;

    // Register Hosted Services
    services.AddHostedService<GracePeriodManagerService>();
    services.AddHostedService<MyHostedServiceB>();
    services.AddHostedService<MyHostedServiceC>();
    //...

【讨论】:

【参考方案8】:

将 IHostedService 切换到 BackgroundService。将 StartAsync 中的任何阻塞代码移动到 ExecuteAsync 并在 StartAsync 结束时返回 base.StartAsync(cancellationToken)。

【讨论】:

以上是关于IHostedService 的多种实现的主要内容,如果未能解决你的问题,请参考以下文章

text 实现IHostedService用于实现后台服务,类似while(true)的基类

Webservice实现与调用(基于Spring的多种方式)

如何在 ASP.Net Core 中使用 IHostedService

在后台主机中托管SignalR服务并广播心跳包

我应该如何将 DbContext 实例注入 IHostedService?

IHostedService 可用于 Azure Functions 应用程序吗?