概述
内置一个简单的作业管理器。可以用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 <= 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
支持 持久化作业信息
提供了一个可视化的仪表盘,用于监控和管理后台作业
支持自动重试
可使用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 的选项
- ServerOptions:
-
- 类型:BackgroundJobServerOptions
- 用途:配置 Hangfire 服务器的选项,如队列名称、工作线程数量等。
- AdditionalProcesses:
-
- 类型:IEnumerable
- 用途:额外的背景处理程序列表,可以在这里添加自定义的背景处理程序。
- Storage:
-
- 类型:JobStorage
- 用途:配置 Hangfire 的作业存储,决定任务的存储位置和方式,例如使用 SQL Server、Redis 等。
- 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();
}
}