Правильный способ регистрации HostedService в ASP.NET Core. AddHostedService против AddSingleton

Как правильно зарегистрировать настраиваемую размещенную службу в ASP.NET Core 2.1? Например, у меня есть настраиваемая размещенная служба, производная от BackgroundService с именем MyHostedService. Как мне его зарегистрировать?

public IServiceProvider ConfigureServices(IServiceCollection services)
{           
    //...
    services.AddSingleton<IHostedService, MyHostedService>();
}

or

public IServiceProvider ConfigureServices(IServiceCollection services)
{           
    //...
    services.AddHostedService<MyHostedService>();
}

?

Здесь мы видим первый случай, но здесь есть второй случай.

Эти методы равны?


person Denis Babarykin    schedule 23.07.2018    source источник
comment
более свежие документы рекомендовали вариант секунд   -  person Nkosi    schedule 23.07.2018
comment
Are they equal секунды, которые вызывают первый изнутри, но как временный, а не синглтон   -  person Nkosi    schedule 23.07.2018
comment
Более того, какой из них синглтон, а какой нет. Размещенные службы получают особую обработку со стороны среды выполнения с помощью StartAsync, StopAsync методов. Использование ограниченных / переходных объектов возможно с помощью областей видимости   -  person Panagiotis Kanavos    schedule 23.07.2018


Ответы (2)


Обновлять

Где-то между .Net Core 2.2 и 3.1 поведение изменилось, AddHostedService теперь добавляет Singleton вместо предыдущей службы Transient. Кредит - LeonG

public static class ServiceCollectionHostedServiceExtensions
{
    /// <summary>
    /// Add an <see cref="IHostedService"/> registration for the given type.
    /// </summary>
    /// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
    /// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
    /// <returns>The original <see cref="IServiceCollection"/>.</returns>
    public static IServiceCollection AddHostedService<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors)] THostedService>(this IServiceCollection services)
        where THostedService : class, IHostedService
    {
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService, THostedService>());

        return services;
    }

    /// <summary>
    /// Add an <see cref="IHostedService"/> registration for the given type.
    /// </summary>
    /// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
    /// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
    /// <param name="implementationFactory">A factory to create new instances of the service implementation.</param>
    /// <returns>The original <see cref="IServiceCollection"/>.</returns>
    public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services, Func<IServiceProvider, THostedService> implementationFactory)
        where THostedService : class, IHostedService
    {
        services.TryAddEnumerable(ServiceDescriptor.Singleton<IHostedService>(implementationFactory));

        return services;
    }
}

Ссылка ServiceCollectionHostedServiceExtensions


Оригинальный ответ

Они похожи, но не полностью

AddHostedService является частью Microsoft.Extensions.Hosting.Abstractions.

Он принадлежит Microsoft.Extensions.Hosting.Abstractions в _5 _ класс

using Microsoft.Extensions.Hosting;

namespace Microsoft.Extensions.DependencyInjection
{
    public static class ServiceCollectionHostedServiceExtensions
    {
        /// <summary>
        /// Add an <see cref="IHostedService"/> registration for the given type.
        /// </summary>
        /// <typeparam name="THostedService">An <see cref="IHostedService"/> to register.</typeparam>
        /// <param name="services">The <see cref="IServiceCollection"/> to register with.</param>
        /// <returns>The original <see cref="IServiceCollection"/>.</returns>
        public static IServiceCollection AddHostedService<THostedService>(this IServiceCollection services)
            where THostedService : class, IHostedService
            => services.AddTransient<IHostedService, THostedService>();
    }
}

Обратите внимание, что используется Transient срок службы, а не Singleton

Внутри фреймворк добавляет все размещенные службы в другую службу (HostedServiceExecutor)

public HostedServiceExecutor(ILogger<HostedServiceExecutor> logger, 
    IEnumerable<IHostedService> services) //<<-- note services collection
{
    _logger = logger;
    _services = services;
}

при запуске это синглтон через Конструктор WebHost.

_applicationServiceCollection.AddSingleton<HostedServiceExecutor>();
person Nkosi    schedule 23.07.2018
comment
Это означает, что, несмотря на то, что сервис зарегистрирован как scoped внутри AddHostedService, его время жизни будет таким же, как время жизни приложения, верно? - person Denis Babarykin; 23.07.2018
comment
@Nkosi, как временные службы добавляются в синглтон, разве инжектор зависимостей не должен бросать в этом случае? - person johnny 5; 23.07.2018
comment
@DenisBabarykin да, это будет точно. - person Nkosi; 23.07.2018
comment
@ johnny5 не обязательно. Переходный процесс все равно будет разрешен. Всего один раз синглтоном. :) _ 1_ - person Nkosi; 23.07.2018
comment
@Nkosi, они вручную запрашивают службу, я думал, что поведение изменилось между .Net-Core 1 и .Net-Core 2 - person johnny 5; 23.07.2018
comment
@ johnny5 scoped особенный. синглтон по-прежнему может вызывать временную зависимость времени жизни. Технически переходный процесс сам становится синглтоном, поскольку экземпляр будет жить в синглтоне. - person Nkosi; 23.07.2018
comment
@Nkosi, О, это только для области видимости, Спасибо за это разъяснение, я думал, что это все жизни - person johnny 5; 23.07.2018
comment
Время жизни @ johnny5 в области видимости длится только до тех пор, пока время жизни запроса. Вот почему к нему относятся иначе. - person Nkosi; 23.07.2018
comment
Где-то между .Net Core 2.2 и 3.1 поведение изменилось, AddHostedService теперь добавляет Singleton вместо предыдущей службы Transient. - person LeonG; 10.06.2020

Обновить

В прошлом HostedService был долговременным переходным процессом, эффективно действующим как одноэлементный объект. Начиная с .NET Core 3.1 это настоящий синглтон.


Используйте 1_

Размещенная служба - это больше, чем просто одноэлементная служба. Среда выполнения знает об этом, может сказать ей начать ее вызовом StartAsync или остановить вызовом StopAsync() всякий раз, когда, например, пул приложений перезагружается. Среда выполнения может дождаться завершения работы размещенной службы до завершения работы самого веб-приложения.

Как в документации объясняется, служба с заданной областью может использоваться путем создания области внутри рабочего метода размещенной службы. То же самое и с временными услугами.

Для этого IServicesProvider или IServiceScopeFactory необходимо внедрить в конструктор размещенной службы и использовать для создания области.

Заимствуя из документации, конструктор службы и рабочий метод могут выглядеть следующим образом:

public IServiceProvider Services { get; }

public ConsumeScopedServiceHostedService(IServiceProvider services, 
    ILogger<ConsumeScopedServiceHostedService> logger)
{
    Services = services;
    _logger = logger;
}


private void DoWork()
{
    using (var scope = Services.CreateScope())
    {
        var scopedProcessingService = 
            scope.ServiceProvider
                .GetRequiredService<IScopedProcessingService>();

        scopedProcessingService.DoWork();
    }
}

В этом связанном вопросе показано, как использовать временный DbContext в размещенной службе:

public class MyHostedService : IHostedService
{
    private readonly IServiceScopeFactory scopeFactory;

    public MyHostedService(IServiceScopeFactory scopeFactory)
    {
        this.scopeFactory = scopeFactory;
    }

    public void DoWork()
    {
        using (var scope = scopeFactory.CreateScope())
        {
            var dbContext = scope.ServiceProvider.GetRequiredService<MyDbContext>();
            …
        }
    }
    …
}
person Panagiotis Kanavos    schedule 23.07.2018
comment
Незначительное исправление: размещенная служба на самом деле не является одноэлементной службой. Вы можете вызывать AddHostedService сколько угодно раз для конкретного класса. Среда выполнения запустит несколько экземпляров службы. Это можно использовать с BackgroundService, например, для настройки пула рабочих. - person kayjtea; 12.02.2019
comment
@kayjtea Я не имел в виду, что это так, я отвечал на вопрос. Но похоже, что размещенные сервисы являются одиночными - person Panagiotis Kanavos; 19.01.2021
comment
Теперь он работает так же? Можно ли будет это сделать через Factory и ActivatorUtilites.CreateInstance? - person xSx; 16.06.2021