Множественные реализации IHostedService

Я пытаюсь создать фоновые службы, используя IHostedService. Все работает нормально, если у меня только ОДИН фоновый сервис. Когда я пытаюсь создать более одной реализации IHostedService фактически запускается только тот, который был зарегистрирован первым.

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

В приведенном выше примере StartAsync на HostedServiceOne но StartAsync на HostedServiceTwo никогда не HostedServiceTwo. Если я поменяю порядок регистрации двух реализаций IHostedService (поместите IHostedServiceTwo до IHostedServiceOne), тогда StartAsync на HostedServiceTwo будет вызван, но никогда не будет для HostedServiceOne.

РЕДАКТИРОВАТЬ:

Мне было направлено следующее:

Как зарегистрировать несколько реализаций одного и того же интерфейса в Asp.Net Core?

Однако это не для IHostedService. Чтобы использовать предложенный подход, мне нужно сделать вызов serviceProvider.GetServices<IService>(); но кажется, что IHostedService.StartAsync кажется, называется внутренне. Я даже не уверен, где бы я назвал это, чтобы запустить IHostedService.StartAsync.

+2
источник поделиться
6 ответов

Зарегистрируйте свой HostedService как HostedService ниже:

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

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

[Обновить]:

Смотрите комментарии ниже @nickvane:

Это потому, что первая зарегистрированная служба не возвращает Task для метода StartAsync, поэтому среда выполнения ожидает и не выполняет StartAsync следующего зарегистрированного экземпляра hostedservice.

Вероятно, первый StartAsync() не закончил

+4
источник

У меня такая же проблема. Пришлось возвращать 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>();
    ...
+4
источник

Оставайтесь асинхронными! как обсуждалось здесь...

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);
    }
}   
+2
источник

Похоже, что это можно решить, украсив IHostedService, хотя.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*/)));

Все декораторы будут называться цепным способом, пока не будет следующего!

+1
источник

У меня была такая же проблема, когда несколько реализаций IHostedService не вызывались, а только первый. Проверьте, если вы используете метод TryAdd в методе IWebHostBuilder.ConfigureServices() вместо метода Add. Первая из них не позволяет дублировать реализации интерфейсов, вторая - делает. Это исправило это для меня.

Поэтому используйте это:

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

Вместо этого:

webHost = WebHost
            .CreateDefaultBuilder()
            .ConfigureServices(x => x.TryAdd(serviceCollection))
0
источник

Я столкнулся с немного другой проблемой, однако решение для нее может быть применимо и здесь. Мне нужно было зарегистрировать реализацию сервиса как синглтон и запустить его как 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>>();
0
источник

Посмотрите другие вопросы по меткам или Задайте вопрос