ABP 的后台任务源码详细分析

363 阅读14分钟

概述

内置一个简单的作业管理器。可以用Hangfire、Quartz 作为调度器的实现,rabbitmq 作为任务队列的实现。

ABP 中有BackgroundJob、BackgroundWorker 这两大类后台任务。

相关的包

BackgroundJob 运行后台任务,长时间的任务、可重试持久化的任务

BackgroundWorker,通过独立线程,一般是运行周期性的任务

BackgroundJob

一些注意点

异常:后台作业不应该隐藏异常. 如果它抛出一个异常, 在稍后后台作业将会自动重试. 只有在你不想为当前参数重新运行后台作业时才隐藏异常

使用示例

创建后台作业:继承BackgroundJob,在Execute 方法中执行任务

//作业的参数
public class EmailSendingArgs
{
    public string EmailAddress { get; set; }
    public string Subject { get; set; }
    public string Body { get; set; }
}

public class EmailSendingJob : BackgroundJob<EmailSendingArgs>, ITransientDependency
{
    private readonly IEmailSender _emailSender;

    public EmailSendingJob(IEmailSender emailSender)
    {
        _emailSender = emailSender;
    }

    public override void Execute(EmailSendingArgs args)
    {
        _emailSender.Send(
            args.EmailAddress,
            args.Subject,
            args.Body
        );
    }
}

队列作业:注入 IBackgroundJobManager 服务并且使用它的 EnqueueAsync 方法添加一个新的作业到队列中

public class RegistrationService : ApplicationService
{
    private readonly IBackgroundJobManager _backgroundJobManager;

    public RegistrationService(IBackgroundJobManager backgroundJobManager)
    {
        _backgroundJobManager = backgroundJobManager;
    }

    public async Task RegisterAsync(string userName, string emailAddress, string password)
    {
        //TODO: 创建一个新用户到数据库中...

        await _backgroundJobManager.EnqueueAsync(
            new EmailSendingArgs
            {
                EmailAddress = emailAddress,
                Subject = "You've successfully registered!",
                Body = "..."
            }
        );
    }
}

使用 AbpBackgroundJobOptions 配置作业执行

[DependsOn(typeof(AbpBackgroundJobsModule))]
public class MyModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        Configure<AbpBackgroundJobOptions>(options =>
        {
            options.IsJobExecutionEnabled = false; //禁用作业执行
        });
    }
}

源代码分析

IBackgroundJob

IBackgroundJob 代表一个作业,具有Execute 方法,也有异步版本

BackgroundJob 抽象类,主要包含一个Execute 方法用来执行后台作业

public interface IBackgroundJob<in TArgs>
{
    /// <summary>
    /// Executes the job with the <paramref name="args"/>.
    /// </summary>
    /// <param name="args">Job arguments.</param>
    void Execute(TArgs args);
}

public abstract class BackgroundJob<TArgs> : IBackgroundJob<TArgs>
{
    //TODO: Add UOW, Localization, CancellationTokenProvider and other useful properties..?

    public ILogger<BackgroundJob<TArgs>> Logger { get; set; }

    protected BackgroundJob()
    {
        Logger = NullLogger<BackgroundJob<TArgs>>.Instance;
    }

    public abstract void Execute(TArgs args);
}

异步版本

public interface IAsyncBackgroundJob<in TArgs>
{
    /// <summary>
    /// Executes the job with the <paramref name="args"/>.
    /// </summary>
    /// <param name="args">Job arguments.</param>
    Task ExecuteAsync(TArgs args);
}

public abstract class AsyncBackgroundJob<TArgs> : IAsyncBackgroundJob<TArgs>
{
    //TODO: Add UOW, Localization, CancellationTokenProvider and other useful properties..?

    public ILogger<AsyncBackgroundJob<TArgs>> Logger { get; set; }

    protected AsyncBackgroundJob()
    {
        Logger = NullLogger<AsyncBackgroundJob<TArgs>>.Instance;
    }

    public abstract Task ExecuteAsync(TArgs args);
}

BackgroundJobPriority 作业优先级

public enum BackgroundJobPriority : byte
{
    /// <summary>
    /// Low.
    /// </summary>
    Low = 5,

    /// <summary>
    /// Below normal.
    /// </summary>
    BelowNormal = 10,

    /// <summary>
    /// Normal (default).
    /// </summary>
    Normal = 15,

    /// <summary>
    /// Above normal.
    /// </summary>
    AboveNormal = 20,

    /// <summary>
    /// High.
    /// </summary>
    High = 25
}

作业名称的设计,给作业名称设计一个 Attribute,叫BackgroundJobName。比并提供根据参数类型获取作业名称的方法

GetName接受一个 Type 参数,并从这个类型的属性中获取 IBackgroundJobNameProvider 实现,尝试从其中读取作业的名称。

使用 .GetCustomAttributes(true) 方法获取类型上的所有属性,并过滤出实现了 IBackgroundJobNameProvider 接口的属性。 如果找到相应的属性,则返回其名称;如果没有找到实现了接口的属性,则默认返回类型的全名。

public interface IBackgroundJobNameProvider
{
    string Name { get; }
}

public class BackgroundJobNameAttribute : Attribute, IBackgroundJobNameProvider
{
    public string Name { get; }

    public BackgroundJobNameAttribute([NotNull] string name)
    {
        //确保传入的名称不为空或空白字符串
        Name = Check.NotNullOrWhiteSpace(name, nameof(name));
    }

    public static string GetName<TJobArgs>()
    {
        return GetName(typeof(TJobArgs));
    }

    public static string GetName([NotNull] Type jobArgsType)
    {
        Check.NotNull(jobArgsType, nameof(jobArgsType));

        return (jobArgsType
                    .GetCustomAttributes(true)
                    .OfType<IBackgroundJobNameProvider>()
                    .FirstOrDefault()
                    ?.Name
                ?? jobArgsType.FullName)!;
    }
}

调用侧示例

在这个例子中,EmailSenderJob 类被标记了 BackgroundJobNameAttribute,当需要通过作业管理系统查询或调度这个作业时,可以直接使用名称 "EmailSenderJob" 来引用它。

[BackgroundJobName("EmailSenderJob")]
public class EmailSenderJob : IBackgroundJob<EmailJobArgs>
{
    public void Execute(EmailJobArgs args)
    {
        // 发送电子邮件的逻辑
    }
}

public class EmailJobArgs
{
    public string EmailAddress { get; set; }
    public string Message { get; set; }
}

IBackgroundJobManager

IBackgroundJobManager,用来将后台作业加入到队列的

默认实现是创建一个BackgroundJobInfo 对象,插入到 Store 中

NullBackgroundJobManager 是抽象模块中的实现,如果用这个实现会抛出异常

public interface IBackgroundJobManager
{
    Task<string> EnqueueAsync<TArgs>(
        TArgs args,
        BackgroundJobPriority priority = BackgroundJobPriority.Normal,
        TimeSpan? delay = null
    );
}

protected virtual async Task<Guid> EnqueueAsync(string jobName, object args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null)
{
    var jobInfo = new BackgroundJobInfo
    {
        Id = GuidGenerator.Create(),
        JobName = jobName,
        JobArgs = Serializer.Serialize(args),
        Priority = priority,
        CreationTime = Clock.Now,
        NextTryTime = Clock.Now
    };

    if (delay.HasValue)
    {
        jobInfo.NextTryTime = Clock.Now.Add(delay.Value);
    }

    await Store.InsertAsync(jobInfo);

    return jobInfo.Id;
}

[Dependency(TryRegister = true)]
public class NullBackgroundJobManager : IBackgroundJobManager, ISingletonDependency
{
    public ILogger<NullBackgroundJobManager> Logger { get; set; }

    public NullBackgroundJobManager()
    {
        Logger = NullLogger<NullBackgroundJobManager>.Instance;
    }

    public virtual Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal,
        TimeSpan? delay = null)
    {
        throw new AbpException("Background job system has not a real implementation. If it's mandatory, use an implementation (either the default provider or a 3rd party implementation). If it's optional, check IBackgroundJobManager.IsAvailable() extension method and act based on it.");
    }
}

IsAvailable,检查IBackgroundJobManager 是否有一个非空的实现。 用于检查后台作业系统是否有一个实际的(非空的)实现 。

ProxyHelper.UnProxy 方法,该方法的作用是解包可能由代理封装的对象。这通常用于动态代理场景,其中实例可能被框架为了某些功能(如延迟加载、拦截等)而动态包装。

public static class BackgroundJobManagerExtensions
{
    /// <summary>
    /// Checks if background job system has a real implementation.
    /// It returns false if the current implementation is <see cref="NullBackgroundJobManager"/>.
    /// </summary>
    /// <param name="backgroundJobManager"></param>
    /// <returns></returns>
    public static bool IsAvailable(this IBackgroundJobManager backgroundJobManager)
    {
        return !(ProxyHelper.UnProxy(backgroundJobManager) is NullBackgroundJobManager);
    }
}

将作业加入到后台作业时,需要更多作业详细信息,定义一个类BackgroundJobInfo

public class BackgroundJobInfo
{
    public Guid Id { get; set; }

    /// <summary>
    /// Name of the job.
    /// </summary>
    public virtual string JobName { get; set; } = default!;

    /// <summary>
    /// Job arguments as serialized to string.
    /// </summary>
    public virtual string JobArgs { get; set; } = default!;

    /// <summary>
    /// Try count of this job.
    /// A job is re-tried if it fails.
    /// </summary>
    public virtual short TryCount { get; set; }

    /// <summary>
    /// Creation time of this job.
    /// </summary>
    public virtual DateTime CreationTime { get; set; }

    /// <summary>
    /// Next try time of this job.
    /// </summary>
    public virtual DateTime NextTryTime { get; set; }

    /// <summary>
    /// Last try time of this job.
    /// </summary>
    public virtual DateTime? LastTryTime { get; set; }

    /// <summary>
    /// This is true if this job is continuously failed and will not be executed again.
    /// </summary>
    public virtual bool IsAbandoned { get; set; }

    /// <summary>
    /// Priority of this job.
    /// </summary>
    public virtual BackgroundJobPriority Priority { get; set; }

    /// <summary>
    /// Initializes a new instance of the <see cref="BackgroundJobInfo"/> class.
    /// </summary>
    public BackgroundJobInfo()
    {
        Priority = BackgroundJobPriority.Normal;
    }
}

配置类Options

AbpBackgroundJobOptions 配置类,包含类型跟具体的配置类对应关系的字典,以及获取、添加配置的方法。还有作业是否启用

AbpBackgroundJobOptions

private readonly Dictionary<Type, BackgroundJobConfiguration> _jobConfigurationsByArgsType;
private readonly Dictionary<string, BackgroundJobConfiguration> _jobConfigurationsByName;
public bool IsJobExecutionEnabled { get; set; } = true;

BackgroundJobConfiguration

public class BackgroundJobConfiguration
{
    public Type ArgsType { get; }

    public Type JobType { get; }

    public string JobName { get; }

    public BackgroundJobConfiguration(Type jobType)
    {
        JobType = jobType;
        ArgsType = BackgroundJobArgsHelper.GetJobArgsType(jobType);
        JobName = BackgroundJobNameAttribute.GetName(ArgsType);
    }
}

BackgroundJobConfiguration,见上

作业什么时候被加入?在PreConfigureServices 时配置依赖关系时,通过反射把作业加入到 AbpBackgroundJobOptions 中

[DependsOn(
    typeof(AbpJsonModule),
    typeof(AbpMultiTenancyAbstractionsModule)
    )]
public class AbpBackgroundJobsAbstractionsModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        RegisterJobs(context.Services);
    }

    private static void RegisterJobs(IServiceCollection services)
    {
        var jobTypes = new List<Type>();

        services.OnRegistered(context =>
        {
            if (ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IBackgroundJob<>)) ||
                ReflectionHelper.IsAssignableToGenericType(context.ImplementationType, typeof(IAsyncBackgroundJob<>)))
            {
                jobTypes.Add(context.ImplementationType);
            }
        });

        services.Configure<AbpBackgroundJobOptions>(options =>
        {
            foreach (var jobType in jobTypes)
            {
                options.AddJob(jobType);
            }
        });
    }
}

作业存储

作业的存储,考虑到可能被序列化为 json 或其他类型,定义IBackgroundJobSerializer 和 Json 的实现

public interface IBackgroundJobSerializer
{
    string Serialize(object obj);

    object Deserialize(string value, Type type);
}

public class JsonBackgroundJobSerializer : IBackgroundJobSerializer, ITransientDependency
{
    private readonly IJsonSerializer _jsonSerializer;

    public JsonBackgroundJobSerializer(IJsonSerializer jsonSerializer)
    {
        _jsonSerializer = jsonSerializer;
    }

    public string Serialize(object obj)
    {
        return _jsonSerializer.Serialize(obj);
    }

    public object Deserialize(string value, Type type)
    {
        return _jsonSerializer.Deserialize(type, value);
    }

    public T Deserialize<T>(string value)
    {
        return _jsonSerializer.Deserialize<T>(value);
    }
}

IBackgroundJobStore,在默认实现模块中,主要是对作业增删改查的

public interface IBackgroundJobStore
{
    /// <summary>
    /// Gets a BackgroundJobInfo based on the given jobId.
    /// </summary>
    /// <param name="jobId">The Job Unique Identifier.</param>
    /// <returns>The BackgroundJobInfo object.</returns>
    Task<BackgroundJobInfo> FindAsync(Guid jobId);

    /// <summary>
    /// Inserts a background job.
    /// </summary>
    /// <param name="jobInfo">Job information.</param>
    Task InsertAsync(BackgroundJobInfo jobInfo);

    /// <summary>
    /// Gets waiting jobs. It should get jobs based on these:
    /// Conditions: !IsAbandoned And NextTryTime &lt;= Clock.Now.
    /// Order by: Priority DESC, TryCount ASC, NextTryTime ASC.
    /// Maximum result: <paramref name="maxResultCount"/>.
    /// </summary>
    /// <param name="maxResultCount">Maximum result count.</param>
    Task<List<BackgroundJobInfo>> GetWaitingJobsAsync(int maxResultCount);

    /// <summary>
    /// Deletes a job.
    /// </summary>
    /// <param name="jobId">The Job Unique Identifier.</param>
    Task DeleteAsync(Guid jobId);

    /// <summary>
    /// Updates a job.
    /// </summary>
    /// <param name="jobInfo">Job information.</param>
    Task UpdateAsync(BackgroundJobInfo jobInfo);
}

InMemoryBackgroundJobStore ,在内存中的实现,用一个字典来存

public class InMemoryBackgroundJobStore : IBackgroundJobStore, ISingletonDependency
{
    private readonly ConcurrentDictionary<Guid, BackgroundJobInfo> _jobs;

    protected IClock Clock { get; }

    /// <summary>
    /// Initializes a new instance of the <see cref="InMemoryBackgroundJobStore"/> class.
    /// </summary>
    public InMemoryBackgroundJobStore(IClock clock)
    {
        Clock = clock;
        _jobs = new ConcurrentDictionary<Guid, BackgroundJobInfo>();
    }

    public virtual Task<BackgroundJobInfo> FindAsync(Guid jobId)
    {
        return Task.FromResult(_jobs.GetOrDefault(jobId))!;
    }

    public virtual Task InsertAsync(BackgroundJobInfo jobInfo)
    {
        _jobs[jobInfo.Id] = jobInfo;

        return Task.CompletedTask;
    }

    public virtual Task<List<BackgroundJobInfo>> GetWaitingJobsAsync(int maxResultCount)
    {
        var waitingJobs = _jobs.Values
            .Where(t => !t.IsAbandoned && t.NextTryTime <= Clock.Now)
            .OrderByDescending(t => t.Priority)
            .ThenBy(t => t.TryCount)
            .ThenBy(t => t.NextTryTime)
            .Take(maxResultCount)
            .ToList();

        return Task.FromResult(waitingJobs);
    }


    public virtual Task DeleteAsync(Guid jobId)
    {
        _jobs.TryRemove(jobId, out _);

        return Task.CompletedTask;
    }

    public virtual Task UpdateAsync(BackgroundJobInfo jobInfo)
    {
        if (jobInfo.IsAbandoned)
        {
            return DeleteAsync(jobInfo.Id);
        }

        return Task.CompletedTask;
    }
}

作业执行器

IBackgroundJobExecuter,作业执行的具体逻辑,主要是通过反射调用作业的 Execute 方法

public interface IBackgroundJobExecuter
{
    Task ExecuteAsync(JobExecutionContext context);
}

public class BackgroundJobExecuter : IBackgroundJobExecuter, ITransientDependency
{
    public ILogger<BackgroundJobExecuter> Logger { protected get; set; }

    protected AbpBackgroundJobOptions Options { get; }
    
    protected ICurrentTenant CurrentTenant { get; }

    public BackgroundJobExecuter(IOptions<AbpBackgroundJobOptions> options, ICurrentTenant currentTenant)
    {
        CurrentTenant = currentTenant;
        Options = options.Value;

        Logger = NullLogger<BackgroundJobExecuter>.Instance;
    }

    public virtual async Task ExecuteAsync(JobExecutionContext context)
    {
        var job = context.ServiceProvider.GetService(context.JobType);
        if (job == null)
        {
            throw new AbpException("The job type is not registered to DI: " + context.JobType);
        }

        var jobExecuteMethod = context.JobType.GetMethod(nameof(IBackgroundJob<object>.Execute)) ??
                               context.JobType.GetMethod(nameof(IAsyncBackgroundJob<object>.ExecuteAsync));
        if (jobExecuteMethod == null)
        {
            throw new AbpException($"Given job type does not implement {typeof(IBackgroundJob<>).Name} or {typeof(IAsyncBackgroundJob<>).Name}. " +
                                   "The job type was: " + context.JobType);
        }

        try
        {
            using(CurrentTenant.Change(GetJobArgsTenantId(context.JobArgs)))
            {
                var cancellationTokenProvider =
                    context.ServiceProvider.GetRequiredService<ICancellationTokenProvider>();

                using (cancellationTokenProvider.Use(context.CancellationToken))
                {
                    if (jobExecuteMethod.Name == nameof(IAsyncBackgroundJob<object>.ExecuteAsync))
                    {
                        await ((Task)jobExecuteMethod.Invoke(job, new[] { context.JobArgs })!);
                    }
                    else
                    {
                        jobExecuteMethod.Invoke(job, new[] { context.JobArgs });
                    }
                }
            }
           
        }
        catch (Exception ex)
        {
            Logger.LogException(ex);

            await context.ServiceProvider
                .GetRequiredService<IExceptionNotifier>()
                .NotifyAsync(new ExceptionNotificationContext(ex));

            throw new BackgroundJobExecutionException("A background job execution is failed. See inner exception for details.", ex)
            {
                JobType = context.JobType.AssemblyQualifiedName!,
                JobArgs = context.JobArgs
            };
        }
    }
    
    protected virtual Guid? GetJobArgsTenantId(object jobArgs)
    {
        return jobArgs switch
        {
            IMultiTenant multiTenantJobArgs => multiTenantJobArgs.TenantId,
            _ => CurrentTenant.Id
        };
    }
}

执行传递的Context JobExecutionContext ,包含作业类型、参数

public class JobExecutionContext : IServiceProviderAccessor
{
    public IServiceProvider ServiceProvider { get; }

    public Type JobType { get; }

    public object JobArgs { get; }

    public CancellationToken CancellationToken { get; }

    public JobExecutionContext(
        IServiceProvider serviceProvider,
        Type jobType,
        object jobArgs,
        CancellationToken cancellationToken = default)
    {
        ServiceProvider = serviceProvider;
        JobType = jobType;
        JobArgs = jobArgs;
        CancellationToken = cancellationToken;
    }
}

执行结果, JobExecutionResult 枚举

public enum JobExecutionResult
{
    Success,
    Failed
}

执行发生的异常BackgroundJobExecutionException

public class BackgroundJobExecutionException : AbpException
{
    public string JobType { get; set; } = default!;

    public object JobArgs { get; set; } = default!;

    public BackgroundJobExecutionException()
    {

    }

    /// <summary>
    /// Creates a new <see cref="BackgroundJobExecutionException"/> object.
    /// </summary>
    /// <param name="message">Exception message</param>
    /// <param name="innerException">Inner exception</param>
    public BackgroundJobExecutionException(string message, Exception innerException)
        : base(message, innerException)
    {

    }
}

抛出异常的示例

throw new BackgroundJobExecutionException("A background job execution is failed. See inner exception for details.", ex)
{
    JobType = context.JobType.AssemblyQualifiedName!,
    JobArgs = context.JobArgs
};

作业什么时候执行?注入IBackgroundJobExecuter 即可执行

调用侧示例

var args = JsonSerializer.Deserialize<TArgs>(context.JobDetail.JobDataMap.GetString(nameof(TArgs))!);
var jobType = Options.GetJob(typeof(TArgs)).JobType;
var jobContext = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: context.CancellationToken);
try
{
    await JobExecuter.ExecuteAsync(jobContext);
}

其他

工具方法

BackgroundJobArgsHelper.GetJobArgsType 根据作业类型获取参数类型

遍历 jobType 实现的所有接口。这是为了找出实现了 IBackgroundJob<> 或 IAsyncBackgroundJob<> 这两个泛型接口的接口

public static class BackgroundJobArgsHelper
{
    public static Type GetJobArgsType(Type jobType)
    {
        foreach (var @interface in jobType.GetInterfaces())
        {
            //检查当前遍历的接口是否为泛型,如果不是,则继续遍历
            if (!@interface.IsGenericType)
            {
                continue;
            }

            if (@interface.GetGenericTypeDefinition() != typeof(IBackgroundJob<>) &&
                @interface.GetGenericTypeDefinition() != typeof(IAsyncBackgroundJob<>))
            {
                continue;
            }
            //提取该泛型接口的泛型参数
            var genericArgs = @interface.GetGenericArguments();
            if (genericArgs.Length != 1)
            {
                continue;
            }

            return genericArgs[0];
        }

        throw new AbpException($"Could not find type of the job args. " +
                               $"Ensure that given type implements the {typeof(IBackgroundJob<>).AssemblyQualifiedName} or {typeof(IAsyncBackgroundJob<>).AssemblyQualifiedName} interface. " +
                               $"Given job type: {jobType.AssemblyQualifiedName}");
    }
}

AbpBackgroundJobWorkerOptions,job 的一些配置,提供默认值等信息

public class AbpBackgroundJobWorkerOptions
{
    /// <summary>
    /// Interval between polling jobs from <see cref="IBackgroundJobStore"/>.
    /// Default value: 5000 (5 seconds).
    /// </summary>
    public int JobPollPeriod { get; set; }

    /// <summary>
    /// Maximum count of jobs to fetch from data store in one loop.
    /// Default: 1000.
    /// </summary>
    public int MaxJobFetchCount { get; set; }

    /// <summary>
    /// Default duration (as seconds) for the first wait on a failure.
    /// Default value: 60 (1 minutes).
    /// </summary>
    public int DefaultFirstWaitDuration { get; set; }

    /// <summary>
    /// Default timeout value (as seconds) for a job before it's abandoned (<see cref="BackgroundJobInfo.IsAbandoned"/>).
    /// Default value: 172,800 (2 days).
    /// </summary>
    public int DefaultTimeout { get; set; }

    /// <summary>
    /// Default wait factor for execution failures.
    /// This amount is multiplated by last wait time to calculate next wait time.
    /// Default value: 2.0.
    /// </summary>
    public double DefaultWaitFactor { get; set; }

    public AbpBackgroundJobWorkerOptions()
    {
        MaxJobFetchCount = 1000;
        JobPollPeriod = 5000;
        DefaultFirstWaitDuration = 60;
        DefaultTimeout = 172800;
        DefaultWaitFactor = 2.0;
    }
}

IBackgroundJobWorker、BackgroundJobWorker,提供了执行任务、更新作业信息、计算下次重试时间的方法。其中执行任务的核心逻辑是先从 store 中拿出等待执行的作业信息,反序列化为具体的 job,然后调用IBackgroundJobExecuter 取执行作业,store 中删除这个作业。IBackgroundWorker 被注册后,程序启动就会执行,AsyncPeriodicBackgroundWorkerBase里面有个定时器,所以会周期执行

public interface IBackgroundJobWorker : IBackgroundWorker
{

}

public class BackgroundJobWorker : AsyncPeriodicBackgroundWorkerBase, IBackgroundJobWorker
{
    protected const string DistributedLockName = "AbpBackgroundJobWorker";

    protected AbpBackgroundJobOptions JobOptions { get; }

    protected AbpBackgroundJobWorkerOptions WorkerOptions { get; }

    protected IAbpDistributedLock DistributedLock { get; }

    public BackgroundJobWorker(
        AbpAsyncTimer timer,
        IOptions<AbpBackgroundJobOptions> jobOptions,
        IOptions<AbpBackgroundJobWorkerOptions> workerOptions,
        IServiceScopeFactory serviceScopeFactory,
        IAbpDistributedLock distributedLock)
        : base(
            timer,
            serviceScopeFactory)
    {
        DistributedLock = distributedLock;
        WorkerOptions = workerOptions.Value;
        JobOptions = jobOptions.Value;
        Timer.Period = WorkerOptions.JobPollPeriod;
    }

    protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
    {
        await using (var handler = await DistributedLock.TryAcquireAsync(DistributedLockName, cancellationToken: StoppingToken))
        {
            if (handler != null)
            {
                var store = workerContext.ServiceProvider.GetRequiredService<IBackgroundJobStore>();

                var waitingJobs = await store.GetWaitingJobsAsync(WorkerOptions.MaxJobFetchCount);

                if (!waitingJobs.Any())
                {
                    return;
                }

                var jobExecuter = workerContext.ServiceProvider.GetRequiredService<IBackgroundJobExecuter>();
                var clock = workerContext.ServiceProvider.GetRequiredService<IClock>();
                var serializer = workerContext.ServiceProvider.GetRequiredService<IBackgroundJobSerializer>();

                foreach (var jobInfo in waitingJobs)
                {
                    jobInfo.TryCount++;
                    jobInfo.LastTryTime = clock.Now;

                    try
                    {
                        var jobConfiguration = JobOptions.GetJob(jobInfo.JobName);
                        var jobArgs = serializer.Deserialize(jobInfo.JobArgs, jobConfiguration.ArgsType);
                        var context = new JobExecutionContext(
                            workerContext.ServiceProvider,
                            jobConfiguration.JobType,
                            jobArgs,
                            workerContext.CancellationToken);

                        try
                        {
                            await jobExecuter.ExecuteAsync(context);

                            await store.DeleteAsync(jobInfo.Id);
                        }
                        catch (BackgroundJobExecutionException)
                        {
                            var nextTryTime = CalculateNextTryTime(jobInfo, clock);

                            if (nextTryTime.HasValue)
                            {
                                jobInfo.NextTryTime = nextTryTime.Value;
                            }
                            else
                            {
                                jobInfo.IsAbandoned = true;
                            }

                            await TryUpdateAsync(store, jobInfo);
                        }
                    }
                    catch (Exception ex)
                    {
                        Logger.LogException(ex);
                        jobInfo.IsAbandoned = true;
                        await TryUpdateAsync(store, jobInfo);
                    }
                }
            }
            else
            {
                try
                {
                    await Task.Delay(WorkerOptions.JobPollPeriod * 12, StoppingToken);
                }
                catch (TaskCanceledException) { }
            }
        }
    }

    protected virtual async Task TryUpdateAsync(IBackgroundJobStore store, BackgroundJobInfo jobInfo)
    {
        try
        {
            await store.UpdateAsync(jobInfo);
        }
        catch (Exception updateEx)
        {
            Logger.LogException(updateEx);
        }
    }

    protected virtual DateTime? CalculateNextTryTime(BackgroundJobInfo jobInfo, IClock clock)
    {
        var nextWaitDuration = WorkerOptions.DefaultFirstWaitDuration *
                               (Math.Pow(WorkerOptions.DefaultWaitFactor, jobInfo.TryCount - 1));
        var nextTryDate = jobInfo.LastTryTime?.AddSeconds(nextWaitDuration) ??
                          clock.Now.AddSeconds(nextWaitDuration);

        if (nextTryDate.Subtract(jobInfo.CreationTime).TotalSeconds > WorkerOptions.DefaultTimeout)
        {
            return null;
        }

        return nextTryDate;
    }
}

BackgroundJobWorker

使用示例

使用示例

继承IBackgroundWorker 或BackgroundWorkerBase

public class MyWorker : BackgroundWorkerBase
{
    public override Task StartAsync(CancellationToken cancellationToken = default)
    {
        //...
    }

    public override Task StopAsync(CancellationToken cancellationToken = default)
    {
        //...
    }
}

AsyncPeriodicBackgroundWorkerBase 使用示例,定期的后台任务

public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase
{
    public PassiveUserCheckerWorker(
            AbpAsyncTimer timer,
            IServiceScopeFactory serviceScopeFactory
        ) : base(
            timer, 
            serviceScopeFactory)
    {
        Timer.Period = 600000; //10 minutes
    }

    protected async override Task DoWorkAsync(
        PeriodicBackgroundWorkerContext workerContext)
    {
        Logger.LogInformation("Starting: Setting status of inactive users...");

        //Resolve dependencies
        var userRepository = workerContext
            .ServiceProvider
            .GetRequiredService<IUserRepository>();

        //Do the work
        await userRepository.UpdateInactiveUserStatusesAsync();

        Logger.LogInformation("Completed: Setting status of inactive users...");
    }
}

注册BackgroundWorker

[DependsOn(typeof(AbpBackgroundWorkersModule))]
public class MyModule : AbpModule
{
    public override async Task OnApplicationInitialization(
        ApplicationInitializationContext context)
    {
        await context.AddBackgroundWorkerAsync<PassiveUserCheckerWorker>();
    }
}

核心流程分析

IBackgroundWorker、BackgroundWorkerBase(提供 Start、Stop 方法)

AsyncPeriodicBackgroundWorkerBase,代表定期的后台任务,提供DoWorkAsync 方法,通过AbpAsyncTimer定时器执行。PeriodicBackgroundWorkerContext,代表传递给DoWorkAsync 方法的参数。

IBackgroundWorkerManager ,BackgroundWorkerManager 实现中主要提供了一个IBackgroundWorker 实现类的列表,添加 Worker,Start Stop 的方法

配置类AbpBackgroundWorkerOptions,提供IsEnabled 属性,是否禁用BackgroundWorker。

AbpBackgroundWorkersModule,程序启动时 Start 所有IBackgroundWorker,程序关闭时结束所有IBackgroundWorker。通过调用IBackgroundWorkerManager 的Start 方法启动(调用每一个IBackgroundWorker 的 Start 方法)

源代码分析

IBackgroundWorker

IBackgroundWorker

/// <summary>
/// Interface for a worker (thread) that runs on background to perform some tasks.
/// </summary>
public interface IBackgroundWorker : IRunnable, ISingletonDependency
{

}

public abstract class BackgroundWorkerBase : IBackgroundWorker
{
    //TODO: Add UOW, Localization and other useful properties..?

    public IAbpLazyServiceProvider LazyServiceProvider { get; set; } = default!;

    public IServiceProvider ServiceProvider { get; set; } = default!;

    protected ILoggerFactory LoggerFactory => LazyServiceProvider.LazyGetRequiredService<ILoggerFactory>();

    protected ILogger Logger => LazyServiceProvider.LazyGetService<ILogger>(provider => LoggerFactory?.CreateLogger(GetType().FullName!) ?? NullLogger.Instance);

    protected CancellationTokenSource StoppingTokenSource { get; }
    protected CancellationToken StoppingToken { get; }

    public BackgroundWorkerBase()
    {
        StoppingTokenSource = new CancellationTokenSource();
        StoppingToken = StoppingTokenSource.Token;
    }

    public virtual Task StartAsync(CancellationToken cancellationToken = default)
    {
        Logger.LogDebug("Started background worker: " + ToString());
        return Task.CompletedTask;
    }

    public virtual Task StopAsync(CancellationToken cancellationToken = default)
    {
        Logger.LogDebug("Stopped background worker: " + ToString());
        StoppingTokenSource.Cancel();
        StoppingTokenSource.Dispose();
        return Task.CompletedTask;
    }

    public override string ToString()
    {
        return GetType().FullName!;
    }
}

AsyncPeriodicBackgroundWorkerBase,代表定期的后台任务。Start 方法中,启动定时器,定时器的定时执行的函数就是 DoWork

public abstract class AsyncPeriodicBackgroundWorkerBase : BackgroundWorkerBase
{
    protected IServiceScopeFactory ServiceScopeFactory { get; }
    protected AbpAsyncTimer Timer { get; }
    protected CancellationToken StartCancellationToken { get; set; }

    protected AsyncPeriodicBackgroundWorkerBase(
        AbpAsyncTimer timer,
        IServiceScopeFactory serviceScopeFactory)
    {
        ServiceScopeFactory = serviceScopeFactory;
        Timer = timer;
        Timer.Elapsed = Timer_Elapsed;
    }

    public async override Task StartAsync(CancellationToken cancellationToken = default)
    {
        StartCancellationToken = cancellationToken;

        await base.StartAsync(cancellationToken);
        Timer.Start(cancellationToken);
    }

    public async override Task StopAsync(CancellationToken cancellationToken = default)
    {
        Timer.Stop(cancellationToken);
        await base.StopAsync(cancellationToken);
    }

    private async Task Timer_Elapsed(AbpAsyncTimer timer)
    {
        await DoWorkAsync(StartCancellationToken);
    }

    private async Task DoWorkAsync(CancellationToken cancellationToken = default)
    {
        using (var scope = ServiceScopeFactory.CreateScope())
        {
            try
            {
                await DoWorkAsync(new PeriodicBackgroundWorkerContext(scope.ServiceProvider, cancellationToken));
            }
            catch (Exception ex)
            {
                await scope.ServiceProvider
                    .GetRequiredService<IExceptionNotifier>()
                    .NotifyAsync(new ExceptionNotificationContext(ex));

                Logger.LogException(ex);
            }
        }
    }

    protected abstract Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext);
}

context

public class PeriodicBackgroundWorkerContext
{
    public IServiceProvider ServiceProvider { get; }

    public CancellationToken CancellationToken { get; }

    public PeriodicBackgroundWorkerContext(IServiceProvider serviceProvider)
    {
        ServiceProvider = serviceProvider;
        CancellationToken = default;
    }

    public PeriodicBackgroundWorkerContext(IServiceProvider serviceProvider, CancellationToken cancellationToken)
    {
        ServiceProvider = serviceProvider;
        CancellationToken = cancellationToken;
    }
}

IBackgroundWorkerManager

IBackgroundWorkerManager ,BackgroundWorkerManager 实现中主要提供了一个IBackgroundWorker 实现类的列表,添加 Worker,Start Stop 的方法

/// <summary>
/// Used to manage background workers.
/// </summary>
public interface IBackgroundWorkerManager : IRunnable
{
    /// <summary>
    /// Adds a new worker. Starts the worker immediately if <see cref="IBackgroundWorkerManager"/> has started.
    /// </summary>
    /// <param name="worker">
    /// The worker. It should be resolved from IOC.
    /// </param>
    /// <param name="cancellationToken"></param>
    Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default);
}

public class BackgroundWorkerManager : IBackgroundWorkerManager, ISingletonDependency, IDisposable
{
    protected bool IsRunning { get; private set; }

    private bool _isDisposed;

    private readonly List<IBackgroundWorker> _backgroundWorkers;

    /// <summary>
    /// Initializes a new instance of the <see cref="BackgroundWorkerManager"/> class.
    /// </summary>
    public BackgroundWorkerManager()
    {
        _backgroundWorkers = new List<IBackgroundWorker>();
    }

    public virtual async Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default)
    {
        _backgroundWorkers.Add(worker);

        if (IsRunning)
        {
            await worker.StartAsync(cancellationToken);
        }
    }

    public virtual void Dispose()
    {
        if (_isDisposed)
        {
            return;
        }

        _isDisposed = true;

        //TODO: ???
    }

    public virtual async Task StartAsync(CancellationToken cancellationToken = default)
    {
        IsRunning = true;

        foreach (var worker in _backgroundWorkers)
        {
            await worker.StartAsync(cancellationToken);
        }
    }

    public virtual async Task StopAsync(CancellationToken cancellationToken = default)
    {
        IsRunning = false;

        foreach (var worker in _backgroundWorkers)
        {
            await worker.StopAsync(cancellationToken);
        }
    }
}

扩展方法,向其中添加IBackgroundWorker

public static class BackgroundWorkersApplicationInitializationContextExtensions
{
    public async static Task<ApplicationInitializationContext> AddBackgroundWorkerAsync<TWorker>([NotNull] this ApplicationInitializationContext context, CancellationToken cancellationToken = default)
        where TWorker : IBackgroundWorker
    {
        Check.NotNull(context, nameof(context));

        await context.AddBackgroundWorkerAsync(typeof(TWorker), cancellationToken: cancellationToken);

        return context;
    }

    public async static Task<ApplicationInitializationContext> AddBackgroundWorkerAsync([NotNull] this ApplicationInitializationContext context, [NotNull] Type workerType, CancellationToken cancellationToken = default)
    {
        Check.NotNull(context, nameof(context));
        Check.NotNull(workerType, nameof(workerType));

        if (!workerType.IsAssignableTo<IBackgroundWorker>())
        {
            throw new AbpException($"Given type ({workerType.AssemblyQualifiedName}) must implement the {typeof(IBackgroundWorker).AssemblyQualifiedName} interface, but it doesn't!");
        }

        await context.ServiceProvider
            .GetRequiredService<IBackgroundWorkerManager>()
            .AddAsync((IBackgroundWorker)context.ServiceProvider.GetRequiredService(workerType), cancellationToken);

        return context;
    }
}

配置类 Options 和 Module

配置类AbpBackgroundWorkerOptions

public class AbpBackgroundWorkerOptions
{
    /// <summary>
    /// Default: true.
    /// </summary>
    public bool IsEnabled { get; set; } = true;
}

AbpBackgroundWorkersModule,程序启动时 Start 所有IBackgroundWorker,程序关闭时结束所有IBackgroundWorker

[DependsOn(
    typeof(AbpThreadingModule)
    )]
public class AbpBackgroundWorkersModule : AbpModule
{
    public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
    {
        var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;
        if (options.IsEnabled)
        {
            await context.ServiceProvider
                .GetRequiredService<IBackgroundWorkerManager>()
                .StartAsync();
        }
    }

    public async override Task OnApplicationShutdownAsync(ApplicationShutdownContext context)
    {
        var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;
        if (options.IsEnabled)
        {
            await context.ServiceProvider
                .GetRequiredService<IBackgroundWorkerManager>()
                .StopAsync();
        }
    }

    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context));
    }

    public override void OnApplicationShutdown(ApplicationShutdownContext context)
    {
        AsyncHelper.RunSync(() => OnApplicationShutdownAsync(context));
    }
}

Hangfire 模块实现

使用示例

依赖这个模块

[DependsOn(
    //...other dependencies
    typeof(AbpBackgroundWorkersHangfireModule) //Add the new module dependency
    )]
public class YourModule : AbpModule
{
}

为Hangfire 配置一个存储

  public override void ConfigureServices(ServiceConfigurationContext context)
  {
      var configuration = context.Services.GetConfiguration();
      var hostingEnvironment = context.Services.GetHostingEnvironment();

      //... other configarations.

      ConfigureHangfire(context, configuration);
  }

  private void ConfigureHangfire(ServiceConfigurationContext context, IConfiguration configuration)
  {
      context.Services.AddHangfire(config =>
      {
          config.UseSqlServerStorage(configuration.GetConnectionString("Default"));
      });
  }

创建一个 worker

public class MyLogWorker : HangfireBackgroundWorkerBase
{
    public MyLogWorker()
    {
        RecurringJobId = nameof(MyLogWorker);
        CronExpression = Cron.Daily();
    }

    public override Task DoWorkAsync(CancellationToken cancellationToken = default)
    {
        Logger.LogInformation("Executed MyLogWorker..!");
        return Task.CompletedTask;
    }
}

注册这个 worker

[DependsOn(typeof(AbpBackgroundWorkersModule))]
public class MyModule : AbpModule
{
    public override async Task OnApplicationInitializationAsync(
        ApplicationInitializationContext context)
    {
        await context.AddBackgroundWorkerAsync<MyLogWorker>();
    }
}

源码分析

BackgroundWorker

IHangfireBackgroundWorker,多了一些Hangfire 特有的参数

public interface IHangfireBackgroundWorker : IBackgroundWorker
{
    string? RecurringJobId { get; set; }

    string CronExpression { get; set; }

    TimeZoneInfo? TimeZone  { get; set; }

    string Queue  { get; set; }

    Task DoWorkAsync(CancellationToken cancellationToken = default);
}

public abstract class HangfireBackgroundWorkerBase : BackgroundWorkerBase, IHangfireBackgroundWorker
{
    public string? RecurringJobId { get; set; }

    public string CronExpression { get; set; } = default!;

    public TimeZoneInfo? TimeZone { get; set; }

    public string Queue { get; set; }

    public abstract Task DoWorkAsync(CancellationToken cancellationToken = default);

    protected HangfireBackgroundWorkerBase()
    {
        TimeZone = null;
        Queue = "default";
    }
}

如果有一个定期清理数据库或检查系统健康状态的任务,可以实现一个继承自 PeriodicBackgroundWorkerBase 或 AsyncPeriodicBackgroundWorkerBase 的类,然后使用这个适配器将其调度为 Hangfire 的周期性任务。

这个类 HangfirePeriodicBackgroundWorkerAdapter 是一个通用适配器,用于将 ABP 的周期性后台工作器 (IBackgroundWorker 接口的实现者) 适配到 Hangfire 的作业调度系统中。这使得可以利用 Hangfire 的强大功能来执行 ABP 定义的周期性任务。

HangfirePeriodicBackgroundWorkerAdapter,周期性后台任务的适配器

public class HangfirePeriodicBackgroundWorkerAdapter<TWorker> : HangfireBackgroundWorkerBase
    where TWorker : IBackgroundWorker
{
    private readonly MethodInfo _doWorkAsyncMethod;
    private readonly MethodInfo _doWorkMethod;

    public HangfirePeriodicBackgroundWorkerAdapter()
    {
        _doWorkAsyncMethod =
            typeof(TWorker).GetMethod("DoWorkAsync", BindingFlags.Instance | BindingFlags.NonPublic)!;
        _doWorkMethod = typeof(TWorker).GetMethod("DoWork", BindingFlags.Instance | BindingFlags.NonPublic)!;
    }

    public async override Task DoWorkAsync(CancellationToken cancellationToken = default)
    {
        var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider, cancellationToken);
        var worker = ServiceProvider.GetRequiredService<TWorker>();

        switch (worker)
        {
            case AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorker:
                await (Task)(_doWorkAsyncMethod.Invoke(asyncPeriodicBackgroundWorker, new object[] { workerContext })!);
                break;
            case PeriodicBackgroundWorkerBase periodicBackgroundWorker:
                _doWorkMethod.Invoke(periodicBackgroundWorker, new object[] { workerContext });
                break;
        }
    }
}

BackgroundManager

RecurringJob 是 Hangfire 的一个核心概念,用于配置和管理重复执行的后台作业。这是 Hangfire 库的一部分,它允许开发者以预定的时间表(使用 Cron 表达式)来调度作业的执行。 AddOrUpdate 是Hangfire 库中的 API,可以指定调用的方法、cron 表达式、时区、队列。

RecurringJob.AddOrUpdate(hangfireBackgroundWorker.RecurringJobId,
    () => ((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(cancellationToken),
    hangfireBackgroundWorker.CronExpression, hangfireBackgroundWorker.TimeZone,
    hangfireBackgroundWorker.Queue);

主要重写了AddAsync 方法,不是加在内存了,而是加在了数据库

[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(IBackgroundWorkerManager), typeof(HangfireBackgroundWorkerManager))]
public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISingletonDependency
{
    protected AbpHangfireBackgroundJobServer BackgroundJobServer { get; set; } = default!;
    protected IServiceProvider ServiceProvider { get; }

    public HangfireBackgroundWorkerManager(IServiceProvider serviceProvider)
    {
        ServiceProvider = serviceProvider;
    }

    public void Initialize()
    {
        BackgroundJobServer = ServiceProvider.GetRequiredService<AbpHangfireBackgroundJobServer>();
    }

    public async override Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default)
    {
        switch (worker)
        {
            case IHangfireBackgroundWorker hangfireBackgroundWorker:
            {
                var unProxyWorker = ProxyHelper.UnProxy(hangfireBackgroundWorker);
                if (hangfireBackgroundWorker.RecurringJobId.IsNullOrWhiteSpace())
                {
                    RecurringJob.AddOrUpdate(
                        () => ((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(cancellationToken),
                        hangfireBackgroundWorker.CronExpression, hangfireBackgroundWorker.TimeZone,
                        hangfireBackgroundWorker.Queue);
                }
                else
                {
                    RecurringJob.AddOrUpdate(hangfireBackgroundWorker.RecurringJobId,
                        () => ((IHangfireBackgroundWorker)unProxyWorker).DoWorkAsync(cancellationToken),
                        hangfireBackgroundWorker.CronExpression, hangfireBackgroundWorker.TimeZone,
                        hangfireBackgroundWorker.Queue);
                }

                break;
            }
            case AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase:
            {
                var timer = worker.GetType()
                    .GetProperty("Timer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(worker);

                var period = worker is AsyncPeriodicBackgroundWorkerBase ? ((AbpAsyncTimer?)timer)?.Period : ((AbpTimer?)timer)?.Period;

                if (period == null)
                {
                    return;
                }

                var adapterType = typeof(HangfirePeriodicBackgroundWorkerAdapter<>).MakeGenericType(ProxyHelper.GetUnProxiedType(worker));
                var workerAdapter = (Activator.CreateInstance(adapterType) as IHangfireBackgroundWorker)!;

                RecurringJob.AddOrUpdate(() => workerAdapter.DoWorkAsync(cancellationToken), GetCron(period.Value), workerAdapter.TimeZone, workerAdapter.Queue);

                break;
            }
            default:
                await base.AddAsync(worker, cancellationToken);
                break;
        }
    }

    protected virtual string GetCron(int period)
    {
        var time = TimeSpan.FromMilliseconds(period);
        string cron;

        if (time.TotalSeconds <= 59)
        {
            cron = $"*/{time.TotalSeconds} * * * * *";
        }
        else if (time.TotalMinutes <= 59)
        {
            cron = $"*/{time.TotalMinutes} * * * *";
        }
        else if (time.TotalHours <= 23)
        {
            cron = $"0 */{time.TotalHours} * * *";
        }
        else
        {
            throw new AbpException(
                $"Cannot convert period: {period} to cron expression, use HangfireBackgroundWorkerBase to define worker");
        }

        return cron;
    }
}

Module

[DependsOn(
    typeof(AbpBackgroundWorkersModule),
    typeof(AbpHangfireModule))]
public class AbpBackgroundWorkersHangfireModule : AbpModule
{
    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        //注册为单例服务
        context.Services.AddSingleton(typeof(HangfirePeriodicBackgroundWorkerAdapter<>));
    }
    
    public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
    {
        var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;
        //检查worker是否启用
        if (!options.IsEnabled)
        {
            var hangfireOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpHangfireOptions>>().Value;
            //没启用则配置了一个只用于任务入队的 Hangfire 服务器,不执行实际的任务处理
            hangfireOptions.BackgroundJobServerFactory = CreateOnlyEnqueueJobServer;
        }
        //Initialize配置和启动 Hangfire 的背景作业服务器。
        context.ServiceProvider
            .GetRequiredService<HangfireBackgroundWorkerManager>()
            .Initialize(); 
    }
    
    private BackgroundJobServer? CreateOnlyEnqueueJobServer(IServiceProvider serviceProvider)
    {
        serviceProvider.GetRequiredService<JobStorage>();
        return null;
    }
}

代码设计值得学习的点

一个后台作业,可以抽象出哪些东西?

  • 具有execute方法的作业:IBackgroundJob、BackgroundJob抽象类、
  • 配置类:AbpBackgroundJobOptions(这个有列表存储所有的作业名称和配置对应关系)、BackgroundJobConfiguration(这个存具体的作业类型、参数类型)
  • 序列化方式:IBackgroundJobSerializer、JsonBackgroundJobSerializer
  • 作业存储:IBackgroundJobStore、InMemoryBackgroundJobStore
  • 持久化对应的实体信息:BackgroundJobInfo
  • 常量和枚举:作业优先级、执行结果JobExecutionResult
  • 作业名称:IBackgroundJobNameProvider、对应的Attribute(BackgroundJobNameAttribute)
  • 作业管理(用来将作业加到队列):IBackgroundJobManager、NullBackgroundJobManager
  • 作业执行器:IBackgroundJobExecuter、BackgroundJobExecuter、JobExecutionContext、执行发生的异常BackgroundJobExecutionException

一个后台Worker,可以抽象出哪些东西?

  • Worker:IBackgroundWorker、BackgroundWorkerBase、AsyncPeriodicBackgroundWorkerBase(周期性的)、PeriodicBackgroundWorkerContext(周期性的上下文)
  • Worker管理:IBackgroundWorkerManager、BackgroundWorkerManager,可以添加woker。
  • 配置类:AbpBackgroundWorkerOptions

第三方库原生用法

选择Hangfire还是Quartz.NET主要取决于应用场景:

  • 对于需要快速实施且带有内置仪表板的简单后台任务管理,Hangfire 是一个好选择。
  • 如果需要复杂的调度策略、集群支持或更高级的功能,Quartz.NET 可能更适合。

Hangfire

github.com/HangfireIO/…

支持 持久化作业信息

提供了一个可视化的仪表盘,用于监控和管理后台作业

支持自动重试

可使用Cron表达式来给定任务执行规则

配置作业存储位置,仪表盘

public void ConfigureServices(IServiceCollection services)
{
    // 添加Hangfire服务
    services.AddHangfire(x => x.UseSqlServerStorage("YourConnectionString"));
    services.AddHangfireServer();
}

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 使用Hangfire仪表盘
    app.UseHangfireDashboard();
}

在应用程序启动时,安排定时作业

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    // 其他配置...

    // 安排每天1点执行的作业
    RecurringJob.AddOrUpdate(
        "PrintMessageJob", // 作业的唯一标识
        () => Console.WriteLine("Hello, World! It's 1 AM now."), // 要执行的方法
        "0 1 * * *"); // Cron表达式,表示每天1点执行
}

public void PrintMessage()
{
    Console.WriteLine("Hello, World! It's 1 AM now.");
}

Quartz.NET

创建了一个StdSchedulerFactory实例来获取一个调度器,然后启动调度器

using Quartz;
using Quartz.Impl;

public class Program
{
    public static async Task Main(string[] args)
    {
        // 创建调度器工厂
        StdSchedulerFactory factory = new StdSchedulerFactory();
        // 获取调度器
        IScheduler scheduler = await factory.GetScheduler();

        // 启动调度器
        await scheduler.Start();

        // 定义作业并将其与PrintMessageJob类关联
        IJobDetail job = JobBuilder.Create<PrintMessageJob>()
            .WithIdentity("PrintMessageJob", "group1") // 给作业和组命名
            .Build();

        // 创建触发器,每天1点执行
        ITrigger trigger = TriggerBuilder.Create()
            .WithIdentity("trigger1", "group1") // 给触发器和组命名
            .WithCronSchedule("0 0 1 * * ?") // Cron表达式,每天凌晨1点执行
            .ForJob(job) // 指定作业
            .Build();

        // 安排作业
        await scheduler.ScheduleJob(job, trigger);
    }
}

定义一个作业

using Quartz;

public class PrintMessageJob : IJob
{
    public Task Execute(IJobExecutionContext context)
    {
        Console.WriteLine("Hello, World! It's 1 AM now.");
        return Task.CompletedTask;
    }
}

ABP 对第三方库的封装

有专用的模块

Hangfire

这里面的东西挺麻烦,先不分析

Options 类,BackgroundJobServer 的选项

  1. ServerOptions
    • 类型:BackgroundJobServerOptions
    • 用途:配置 Hangfire 服务器的选项,如队列名称、工作线程数量等。
  1. AdditionalProcesses
    • 类型:IEnumerable
    • 用途:额外的背景处理程序列表,可以在这里添加自定义的背景处理程序。
  1. Storage
    • 类型:JobStorage
    • 用途:配置 Hangfire 的作业存储,决定任务的存储位置和方式,例如使用 SQL Server、Redis 等。
  1. BackgroundJobServerFactory
    • 类型:Func<IServiceProvider, BackgroundJobServer?>
    • 用途:一个工厂方法,用于创建 BackgroundJobServer 的实例。可以通过这个工厂方法自定义 Hangfire 服务器的创建逻辑,允许在创建过程中注入额外的服务或配置。
  • CreateJobServer
    • 参数:IServiceProvider serviceProvider
    • 返回类型:BackgroundJobServer
    • 功能:这是一个私有方法,用于创建 Hangfire 的 BackgroundJobServer 实例。方法内部首先确保必要的依赖如存储、服务器选项和额外的处理程序已经被配置。如果没有显式配置,它会尝试从服务容器中获取这些依赖。此外,它还从服务提供者中获取或创建必要的 Hangfire 组件,如作业过滤器、作业激活器、作业工厂、执行器和状态更改器。
public class AbpHangfireOptions
{
    public BackgroundJobServerOptions? ServerOptions { get; set; }

    public IEnumerable<IBackgroundProcess>? AdditionalProcesses { get; set; }

    public JobStorage? Storage { get; set; }

    [NotNull]
    public Func<IServiceProvider, BackgroundJobServer?> BackgroundJobServerFactory {
        get => _backgroundJobServerFactory;
        set => _backgroundJobServerFactory = Check.NotNull(value, nameof(value));
    }
    private Func<IServiceProvider, BackgroundJobServer?> _backgroundJobServerFactory;

    public AbpHangfireOptions()
    {
        _backgroundJobServerFactory = CreateJobServer;
    }

    private BackgroundJobServer CreateJobServer(IServiceProvider serviceProvider)
    {
        Storage = Storage ?? serviceProvider.GetRequiredService<JobStorage>();
        ServerOptions = ServerOptions ?? serviceProvider.GetService<BackgroundJobServerOptions>() ?? new BackgroundJobServerOptions();
        AdditionalProcesses = AdditionalProcesses ?? serviceProvider.GetServices<IBackgroundProcess>();

        return new BackgroundJobServer(ServerOptions, Storage, AdditionalProcesses,
            ServerOptions.FilterProvider ?? serviceProvider.GetRequiredService<IJobFilterProvider>(),
            ServerOptions.Activator ?? serviceProvider.GetRequiredService<JobActivator>(),
            serviceProvider.GetService<IBackgroundJobFactory>(),
            serviceProvider.GetService<IBackgroundJobPerformer>(),
            serviceProvider.GetService<IBackgroundJobStateChanger>()
        );
    }
}

JobServer

public class AbpHangfireBackgroundJobServer
{
    public BackgroundJobServer? HangfireJobServer { get; }

    public AbpHangfireBackgroundJobServer(BackgroundJobServer? hangfireJobServer)
    {
        HangfireJobServer = hangfireJobServer;
    }
}

Filter

public class AbpHangfireAuthorizationFilter : IDashboardAsyncAuthorizationFilter
{
    private readonly bool _enableTenant;
    private readonly string? _requiredPermissionName;

    public AbpHangfireAuthorizationFilter(bool enableTenant = false, string? requiredPermissionName = null)
    {
        _enableTenant = requiredPermissionName.IsNullOrWhiteSpace() ? enableTenant : true;
        _requiredPermissionName = requiredPermissionName;
    }

    public async Task<bool> AuthorizeAsync(DashboardContext context)
    {
        if (!IsLoggedIn(context, _enableTenant))
        {
            return false;
        }

        if (_requiredPermissionName.IsNullOrEmpty())
        {
            return true;
        }

        return await IsPermissionGrantedAsync(context, _requiredPermissionName!);
    }

    private static bool IsLoggedIn(DashboardContext context, bool enableTenant)
    {
        var currentUser = context.GetHttpContext().RequestServices.GetRequiredService<ICurrentUser>();

        if (!enableTenant)
        {
            return currentUser.IsAuthenticated && !currentUser.TenantId.HasValue;
        }

        return currentUser.IsAuthenticated;
    }

    private static async Task<bool> IsPermissionGrantedAsync(DashboardContext context, string requiredPermissionName)
    {
        var permissionChecker = context.GetHttpContext().RequestServices.GetRequiredService<IPermissionChecker>();
        return await permissionChecker.IsGrantedAsync(requiredPermissionName);
    }
}

Module

[DependsOn(typeof(AbpAuthorizationAbstractionsModule))]
public class AbpHangfireModule : AbpModule
{
    private AbpHangfireBackgroundJobServer? _backgroundJobServer;

    public override void ConfigureServices(ServiceConfigurationContext context)
    {
        var preActions = context.Services.GetPreConfigureActions<IGlobalConfiguration>();
        context.Services.AddHangfire(configuration =>
        {
            preActions.Configure(configuration);
        });

        context.Services.AddSingleton(serviceProvider =>
        {
            var options = serviceProvider.GetRequiredService<IOptions<AbpHangfireOptions>>().Value;
            return new AbpHangfireBackgroundJobServer(options.BackgroundJobServerFactory.Invoke(serviceProvider));
        });
    }
    public override void OnApplicationInitialization(ApplicationInitializationContext context)
    {
        _backgroundJobServer = context.ServiceProvider.GetRequiredService<AbpHangfireBackgroundJobServer>();
    }

    public override void OnApplicationShutdown(ApplicationShutdownContext context)
    {
        if (_backgroundJobServer == null)
        {
            return;
        }

        _backgroundJobServer.HangfireJobServer?.SendStop();
        _backgroundJobServer.HangfireJobServer?.Dispose();
    }
}

Quartz