如何在后台任务中使用具有依赖性的作用域服务

Posted

技术标签:

【中文标题】如何在后台任务中使用具有依赖性的作用域服务【英文标题】:How to consume scoped service with dependency in a background task 【发布时间】:2019-08-20 04:07:28 【问题描述】:

这是我的场景。我想使用后台任务向订阅用户发送时事通讯。这是由 MailService 完成的,它具有 UnitOfWork 作为依赖项。

我尝试了docs.microsoft.com的解决方案 所以在我的情况下,我使用 IMailService 的方法而不是 ILogger,但我得到一个错误:

System.InvalidOperationException: 'Cannot consume scoped service >'Fit4You.Core.Data.IUnitOfWork' from singleton >'Microsoft.AspNetCore.Hosting.Internal.HostedServiceExecutor'。'

我不想让我的 UnitOfWork 或 DbContext 具有 Singleton 生命周期。 是否有可能以某种方式使用范围为 UnitOfWork 的 MailService 依赖项?

我知道 IServiceScopeFactory 但可能不知道如何正确使用它。

我正在使用 .NET Core 2.2 并内置接口 IHostedService

ScopedMailService:

public class ScopedMailService : IScopedMailService

    private readonly IMailService mailService;

    public ScopedMailService(IMailService mailService)
    
        this.mailService = mailService;
    

    public void DoWork()
    
        mailService.SendNewsletterToSubscribedUsers();
    

ConsumeScopedMailService:

public class ConsumeScopedMailService : IHostedService

    private Timer timer;
    private readonly IMailService mailService;
    public IServiceProvider Services  get; 

    public ConsumeScopedMailService(IServiceProvider services, IMailService mailService)
    
        Services = services;
        this.mailService = mailService;
    

    public Task StartAsync(CancellationToken cancellationToken)
    
        var startTimeSpan = GetStartTimeSpan();
        var periodTimeSpan = TimeSpan.FromSeconds(30);

        timer = new Timer(DoWork, null, startTimeSpan, periodTimeSpan);

        return Task.CompletedTask;
    

    private void DoWork(object state)
    
        using (var scope = Services.CreateScope())
        
            var scopedMailService = scope.ServiceProvider.GetRequiredService<IScopedMailService>();
            scopedMailService.DoWork();
        
    

    public Task StopAsync(CancellationToken cancellationToken)
    
        timer?.Change(Timeout.Infinite, 0);
        return Task.CompletedTask;
    

    public void Dispose()
    
        timer?.Dispose();
    

    private TimeSpan GetStartTimeSpan()
    
        var currentTime = DateTime.Now.Ticks;
        var executeTime = DateTime.Today.AddHours(8)
                                        .AddMinutes(0)
                                        .Ticks;

        long ticks = executeTime - currentTime;

        if (ticks < 0)
        
            ticks = ticks + TimeSpan.TicksPerDay;
        

        var startTimeSpan = new TimeSpan(ticks);

        return startTimeSpan;
    

Startup.cs:

public void ConfigureServices(IServiceCollection services)

    ...

    services.AddDbContext<Fit4YouDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("MyConnectionString")));

    services.AddScoped<IUnitOfWork, UnitOfWork>();
    services.AddTransient<IMailService, MailService>();

    services.AddHostedService<ConsumeScopedMailService>();
    services.AddScoped<IScopedMailService, ScopedMailService>();

    ...

邮件服务:

    public class MailService : IMailService
    
        private readonly IUnitOfWork unitOfWork;
        public MailService(IUnitOfWork unitOfWork)
        
            this.unitOfWork = unitOfWork;
        

        public void SendNewsletterToSubscribedUsers()
        
            // Some Code
        
    

【问题讨论】:

试试这个:***.com/questions/42984052/…。问题是 DI 对对象的生命周期很敏感。您可以使用具有相同或更长生命周期的东西,因为这样您就可以确定您正在使用的对象不会在过程中间被丢弃。 您链接到的文档已经显示了如何执行此操作。有用。该错误抱怨 托管服务 仍然依赖于 IUnitOfWork。您尚未发布该代码,但我怀疑它仍然具有 IUnitOfWork 构造函数参数 @Panagiotis Kanavos 请告诉我您需要哪些代码来定义解决方案 您应该从ConsumeScopedMailService 的构造函数中删除 IMailService mailService 参数。 MailService 间接依赖于作用域 UnitOfWork 服务。无论如何,您的代码从不使用 mailService 值。 @SteveLand 一点也不。 OP 已经使用了该代码,甚至链接到文档。问题是为什么仍然会发生 InvalidOperationException。答案是间接引用了作用域服务 【参考方案1】:

单例ConsumeScopedMailService 通过其构造函数依赖于IMailService

public ConsumeScopedMailService(IServiceProvider services, IMailService mailService)

IMailService 可能是暂时的,但实现它的类依赖于作用域 服务IUnitOfWork。间接地,ConsumeScopedMailService 最终取决于范围服务。

要解决这个问题,IMailService mailService 应该被删除。无论如何,它不会在发布的代码中使用。

【讨论】:

以上是关于如何在后台任务中使用具有依赖性的作用域服务的主要内容,如果未能解决你的问题,请参考以下文章

即使设备在本机反应中重新启动,如何运行后台任务?

将 NSURLSessionDataTask 转换为具有后台支持的下载任务

在 ASP.NET Core 中使用托管服务实现后台任务

使用 Prism 在 Xamarin Forms 的后台服务中实现依赖注入

如何区分后台会话中的下载任务?

如何使用 Swift 3 使用后台任务?