添加使用多项服务的循环作业

Add recurring job that uses multiple services

因此,Hangfire 提供了 IRecurringJobManager 我们可以用来添加重复作业,但它只支持注入一项服务,当我需要访问多项服务时,我找不到任何如何执行此操作的示例。

假设我有两个这样的注册服务:

services.AddTransient<Service1>();
services.AddTransient<Service2>();

而且我想在我的经常性工作中同时使用它们。 理想情况下,我想做的是:

recurringJobManager.AddOrUpdate<Service1, Service2>("my-job", (service1, service2) =>
{
    // do something
}, Cron.Daily);

但这是不可能的。

正确的做法是什么?我应该注入 IServiceProvider 吗?像这样?

recurringJobManager.AddOrUpdate<IServiceProvider>("my-job", (serviceProvider) =>
{
    var service1 = serviceProvider.GetRequiredService<Service1>();
    var service2 = serviceProvider.GetRequiredService<Service2>();
    // do something
}, Cron.Daily);

这行得通吗? 编辑:不,因为我不能在里面使用语句体

我有点困惑,因为文档没有涵盖这些情况。

一个解决方案似乎是创建一个单独的 class 包含我需要的服务

public class MyJob
{
     private readonly Service1 _service1;
     private readonly Service2 _service2;

     public MyJob(Service1 service1, Service2 service2)
     {
         _service1 = service1;
         _service2 = service2;
     }

     public void DoSomething() {}
}

然后像这样使用它:

recurringJobManager.AddOrUpdate<MyJob>("my-job", myJob => myJob.DoSomething(), Cron.Daily);

注意: 我们不能将语句体 {}AddOrUpdate 一起使用。它始终需要是单个方法调用,因为它需要 Expression<T> 作为参数。所以如果我们尝试做 recurringJobManager.AddOrUpdate<MyJob>("my-job", myJob => {myJob.DoSomething(); myJob.DoSomething2();}, Cron.Daily); 我们会得到编译器错误。

另一种解决方案是将 IServiceProvider 作为参数的单独方法。但我不能保证它有效。

// Or just use MyJob without any parameters if it has access to Service1 and Service2 in case they're members of your class.
private void MyJob(IServiceProvider services)
{
    var service1 = services.GetRequiredService<Service1>();
    var service2 = services.GetRequiredService<Service2>();

    // do something
}

// Add like this or just inject `IServiceProvider` somewhere else e.g. in Configure method
recurringJobManager.AddOrUpdate<IServiceProvider>("my-job", services => MyJob(services), Cron.Daily);

同时勾选 https://docs.hangfire.io/en/latest/best-practices.html#best-practices

所以理想情况下你不应该使用 IServiceProvider 作为作业参数,因为它将被序列化。

⚠️ 重要的事情一开始让我非常困惑!

当你这样做时

var myJob = new MyJob();
RecurringJob.AddOrUpdate("my-job", () => myJob.Run(), Cron.Hourly(0));

它不会使用您创建的那个实例,而是每次都创建一个新实例。所以上面的代码 100% 等同于

RecurringJob.AddOrUpdate<MyJob>("my-job", (myJob) => myJob.Run(), Cron.Hourly(0));

因为 AddOrUpdate 在内部所做的是解析表达式、确定 myJob 的类型和调用的方法并构造一个新表达式来创建该类型的实例并在新实例上调用该方法。

不确定这是否符合您的需要,但如果这两个服务具有您正在调用的相同方法(名称),请让它们都实现相同的接口,以便您可以将它们注入构造函数。注意:您必须使用它们的接口注册服务。在构造函数中使用 IEnumerable 进行注入应该为您提供实现该接口的所有服务的实例:

public interface ICronJob
{
    void Run();
}

class Service1 : ICronJob
...

class Service2 : ICronJob
...

public class MyJob
{
    private readonly IEnumerable<ICronJob> _services;

    public MyJob(IEnumerable<ICronJob> services)
    {
        _services = services;
    }

    public void DoSomething() 
    {
        foreach (ICronJob service in services)
            service.Run();
    }

}