From d9fb170bf062fa5e26a95c0cf4212354c254f67b Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 16 Jun 2025 14:13:28 +0800 Subject: [PATCH 1/2] Add support for Cron expressions in background workers and refactor related classes --- .../background-workers/index.md | 3 + .../HangfireBackgroundWorkerManager.cs | 74 ++++++++++++------- .../QuartzPeriodicBackgroundWorkerAdapter.cs | 46 ++++++------ .../AsyncPeriodicBackgroundWorkerBase.cs | 5 ++ .../PeriodicBackgroundWorkerBase.cs | 9 ++- 5 files changed, 86 insertions(+), 51 deletions(-) diff --git a/docs/en/framework/infrastructure/background-workers/index.md b/docs/en/framework/infrastructure/background-workers/index.md index 884ea60c25..707728b0d8 100644 --- a/docs/en/framework/infrastructure/background-workers/index.md +++ b/docs/en/framework/infrastructure/background-workers/index.md @@ -41,6 +41,8 @@ Start your worker in the `StartAsync` (which is called when the application begi Assume that we want to make a user passive, if the user has not logged in to the application in last 30 days. `AsyncPeriodicBackgroundWorkerBase` class simplifies to create periodic workers, so we will use it for the example below: +> You can use `CornExpression` property to set the cron expression for the background worker if you will use the [Hangfire Background Worker Manager](./hangfire.md) or [Quartz Background Worker Manager](./quartz.md). + ````csharp public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase { @@ -52,6 +54,7 @@ public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase serviceScopeFactory) { Timer.Period = 600000; //10 minutes + //CornExpression = "0 0/10 * * * ?"; //Run every 10 minutes, Only for Quartz or Hangfire integration. } protected async override Task DoWorkAsync( diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs index 1ed3561cac..e860415ecd 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs @@ -1,14 +1,15 @@ using System; +using System.Linq.Expressions; using System.Reflection; using System.Threading; using System.Threading.Tasks; using Hangfire; +using Hangfire.Common; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.DynamicProxy; using Volo.Abp.Hangfire; -using Volo.Abp.Threading; namespace Volo.Abp.BackgroundWorkers.Hangfire; @@ -55,40 +56,40 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet } 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; + int? period = null; + string? cornExpression = null; - if (period == null) + if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase) { - return; + period = asyncPeriodicBackgroundWorkerBase.Period; + cornExpression = asyncPeriodicBackgroundWorkerBase.CornExpression; } - - var adapterType = typeof(HangfirePeriodicBackgroundWorkerAdapter<>).MakeGenericType(ProxyHelper.GetUnProxiedType(worker)); - var workerAdapter = (Activator.CreateInstance(adapterType) as IHangfireBackgroundWorker)!; - - if (workerAdapter.RecurringJobId.IsNullOrWhiteSpace()) + else if (worker is PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase) { - RecurringJob.AddOrUpdate( - () => workerAdapter.DoWorkAsync(cancellationToken), - GetCron(period.Value), - workerAdapter.TimeZone , - workerAdapter.Queue.IsNullOrWhiteSpace() ? defaultQueue : defaultQueuePrefix + workerAdapter.Queue); + period = periodicBackgroundWorkerBase.Period; + cornExpression = periodicBackgroundWorkerBase.CornExpression; } - else - { - RecurringJob.AddOrUpdate( - workerAdapter.RecurringJobId, - workerAdapter.Queue.IsNullOrWhiteSpace() ? defaultQueue : defaultQueuePrefix + workerAdapter.Queue, - () => workerAdapter.DoWorkAsync(cancellationToken), - GetCron(period.Value), - new RecurringJobOptions - { - TimeZone = workerAdapter.TimeZone - }); + if (period == null && cornExpression.IsNullOrWhiteSpace()) + { + return; } + var adapterType = typeof(HangfirePeriodicBackgroundWorkerAdapter<>).MakeGenericType(ProxyHelper.GetUnProxiedType(worker)); + var workerAdapter = (Activator.CreateInstance(adapterType) as IHangfireBackgroundWorker)!; + + Expression> methodCall = () => workerAdapter.DoWorkAsync(cancellationToken); + var recurringJobId = !workerAdapter.RecurringJobId.IsNullOrWhiteSpace() ? workerAdapter.RecurringJobId : GetRecurringJobId(worker, methodCall); + RecurringJob.AddOrUpdate( + recurringJobId, + workerAdapter.Queue.IsNullOrWhiteSpace() ? defaultQueue : defaultQueuePrefix + workerAdapter.Queue, + methodCall, + cornExpression ?? GetCron(period!.Value), + new RecurringJobOptions + { + TimeZone = workerAdapter.TimeZone + }); break; } default: @@ -97,6 +98,24 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet } } + private readonly static MethodInfo? GetRecurringJobIdMethodInfo = typeof(RecurringJob).GetMethod("GetRecurringJobId", BindingFlags.NonPublic | BindingFlags.Static); + protected virtual string? GetRecurringJobId(IBackgroundWorker worker, Expression> methodCall) + { + string? recurringJobId = null; + if (GetRecurringJobIdMethodInfo != null) + { + var job = Job.FromExpression(methodCall); + recurringJobId = (string)GetRecurringJobIdMethodInfo.Invoke(null, [job])!; + } + + if (recurringJobId.IsNullOrWhiteSpace()) + { + recurringJobId = $"HangfirePeriodicBackgroundWorkerAdapter<{worker.GetType().Name}>.DoWorkAsync"; + } + + return recurringJobId; + } + protected virtual string GetCron(int period) { var time = TimeSpan.FromMilliseconds(period); @@ -120,8 +139,7 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet } else { - throw new AbpException( - $"Cannot convert period: {period} to cron expression, use HangfireBackgroundWorkerBase to define worker"); + throw new AbpException($"Cannot convert period: {period} to cron expression, use HangfireBackgroundWorkerBase to define worker"); } return cron; diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzPeriodicBackgroundWorkerAdapter.cs b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzPeriodicBackgroundWorkerAdapter.cs index 522ef7a807..2a0051ac8a 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzPeriodicBackgroundWorkerAdapter.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzPeriodicBackgroundWorkerAdapter.cs @@ -3,8 +3,6 @@ using System.Reflection; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; using Quartz; -using Volo.Abp.DynamicProxy; -using Volo.Abp.Threading; namespace Volo.Abp.BackgroundWorkers.Quartz; @@ -25,28 +23,23 @@ public class QuartzPeriodicBackgroundWorkerAdapter : QuartzBackgroundWo } - public void BuildWorker(IBackgroundWorker worker) + public virtual void BuildWorker(IBackgroundWorker worker) { - int? period; - var workerType = ProxyHelper.GetUnProxiedType(worker); + int? period = null; + string? cornExpression = null; - if (worker is AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase) + if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase) { - if (typeof(TWorker) != workerType) - { - throw new ArgumentException($"{nameof(worker)} type is different from the generic type"); - } - - var timer = workerType.GetProperty("Timer", BindingFlags.Instance | BindingFlags.NonPublic)?.GetValue(worker); - - period = worker is AsyncPeriodicBackgroundWorkerBase ? ((AbpAsyncTimer?)timer)?.Period : ((AbpTimer?)timer)?.Period; + period = asyncPeriodicBackgroundWorkerBase.Period; + cornExpression = asyncPeriodicBackgroundWorkerBase.CornExpression; } - else + else if (worker is PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase) { - return; + period = periodicBackgroundWorkerBase.Period; + cornExpression = periodicBackgroundWorkerBase.CornExpression; } - if (period == null) + if (period == null && cornExpression.IsNullOrWhiteSpace()) { return; } @@ -55,10 +48,21 @@ public class QuartzPeriodicBackgroundWorkerAdapter : QuartzBackgroundWo .Create>() .WithIdentity(BackgroundWorkerNameAttribute.GetName()) .Build(); - Trigger = TriggerBuilder.Create() - .WithIdentity(BackgroundWorkerNameAttribute.GetName()) - .WithSimpleSchedule(builder => builder.WithInterval(TimeSpan.FromMilliseconds(period.Value)).RepeatForever()) - .Build(); + + var triggerBuilder = TriggerBuilder.Create() + .ForJob(JobDetail) + .WithIdentity(BackgroundWorkerNameAttribute.GetName()); + + if (!cornExpression.IsNullOrWhiteSpace()) + { + triggerBuilder.WithCronSchedule(cornExpression); + } + else + { + triggerBuilder.WithSimpleSchedule(builder => builder.WithInterval(TimeSpan.FromMilliseconds(period!.Value)).RepeatForever()); + } + + Trigger = triggerBuilder.Build(); } public async override Task Execute(IJobExecutionContext context) diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs index 37911feae5..938bd1d2a2 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs @@ -13,6 +13,11 @@ public abstract class AsyncPeriodicBackgroundWorkerBase : BackgroundWorkerBase protected IServiceScopeFactory ServiceScopeFactory { get; } protected AbpAsyncTimer Timer { get; } protected CancellationToken StartCancellationToken { get; set; } + public int Period => Timer.Period; + /// + /// CornExpression has high priority over Period. + /// + public string? CornExpression { get; protected set; } protected AsyncPeriodicBackgroundWorkerBase( AbpAsyncTimer timer, diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs index 9d78d9237d..95fd8b342a 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs @@ -15,6 +15,11 @@ public abstract class PeriodicBackgroundWorkerBase : BackgroundWorkerBase { protected IServiceScopeFactory ServiceScopeFactory { get; } protected AbpTimer Timer { get; } + public int Period => Timer.Period; + /// + /// CornExpression has high priority over Period. + /// + public string? CornExpression { get; protected set; } protected PeriodicBackgroundWorkerBase( AbpTimer timer, @@ -25,13 +30,13 @@ public abstract class PeriodicBackgroundWorkerBase : BackgroundWorkerBase Timer.Elapsed += Timer_Elapsed; } - public override async Task StartAsync(CancellationToken cancellationToken = default) + public async override Task StartAsync(CancellationToken cancellationToken = default) { await base.StartAsync(cancellationToken); Timer.Start(cancellationToken); } - public override async Task StopAsync(CancellationToken cancellationToken = default) + public async override Task StopAsync(CancellationToken cancellationToken = default) { Timer.Stop(cancellationToken); await base.StopAsync(cancellationToken); From 5262828dd37aa49f95e2372f837d197598102779 Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 16 Jun 2025 15:06:23 +0800 Subject: [PATCH 2/2] Fix typo in property name from 'CornExpression' to 'CronExpression' in background worker classes --- .../infrastructure/background-workers/index.md | 4 ++-- .../Hangfire/HangfireBackgroundWorkerManager.cs | 10 +++++----- .../Quartz/QuartzPeriodicBackgroundWorkerAdapter.cs | 12 ++++++------ .../AsyncPeriodicBackgroundWorkerBase.cs | 4 ++-- .../PeriodicBackgroundWorkerBase.cs | 4 ++-- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/en/framework/infrastructure/background-workers/index.md b/docs/en/framework/infrastructure/background-workers/index.md index 707728b0d8..6ab88a9e53 100644 --- a/docs/en/framework/infrastructure/background-workers/index.md +++ b/docs/en/framework/infrastructure/background-workers/index.md @@ -41,7 +41,7 @@ Start your worker in the `StartAsync` (which is called when the application begi Assume that we want to make a user passive, if the user has not logged in to the application in last 30 days. `AsyncPeriodicBackgroundWorkerBase` class simplifies to create periodic workers, so we will use it for the example below: -> You can use `CornExpression` property to set the cron expression for the background worker if you will use the [Hangfire Background Worker Manager](./hangfire.md) or [Quartz Background Worker Manager](./quartz.md). +> You can use `CronExpression` property to set the cron expression for the background worker if you will use the [Hangfire Background Worker Manager](./hangfire.md) or [Quartz Background Worker Manager](./quartz.md). ````csharp public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase @@ -54,7 +54,7 @@ public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase serviceScopeFactory) { Timer.Period = 600000; //10 minutes - //CornExpression = "0 0/10 * * * ?"; //Run every 10 minutes, Only for Quartz or Hangfire integration. + //CronExpression = "0 0/10 * * * ?"; //Run every 10 minutes, Only for Quartz or Hangfire integration. } protected async override Task DoWorkAsync( diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs index e860415ecd..fe9a8ad983 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Hangfire/Volo/Abp/BackgroundWorkers/Hangfire/HangfireBackgroundWorkerManager.cs @@ -57,20 +57,20 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet case AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase: { int? period = null; - string? cornExpression = null; + string? CronExpression = null; if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase) { period = asyncPeriodicBackgroundWorkerBase.Period; - cornExpression = asyncPeriodicBackgroundWorkerBase.CornExpression; + CronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression; } else if (worker is PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase) { period = periodicBackgroundWorkerBase.Period; - cornExpression = periodicBackgroundWorkerBase.CornExpression; + CronExpression = periodicBackgroundWorkerBase.CronExpression; } - if (period == null && cornExpression.IsNullOrWhiteSpace()) + if (period == null && CronExpression.IsNullOrWhiteSpace()) { return; } @@ -85,7 +85,7 @@ public class HangfireBackgroundWorkerManager : BackgroundWorkerManager, ISinglet recurringJobId, workerAdapter.Queue.IsNullOrWhiteSpace() ? defaultQueue : defaultQueuePrefix + workerAdapter.Queue, methodCall, - cornExpression ?? GetCron(period!.Value), + CronExpression ?? GetCron(period!.Value), new RecurringJobOptions { TimeZone = workerAdapter.TimeZone diff --git a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzPeriodicBackgroundWorkerAdapter.cs b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzPeriodicBackgroundWorkerAdapter.cs index 2a0051ac8a..2967b1a508 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzPeriodicBackgroundWorkerAdapter.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers.Quartz/Volo/Abp/BackgroundWorkers/Quartz/QuartzPeriodicBackgroundWorkerAdapter.cs @@ -26,20 +26,20 @@ public class QuartzPeriodicBackgroundWorkerAdapter : QuartzBackgroundWo public virtual void BuildWorker(IBackgroundWorker worker) { int? period = null; - string? cornExpression = null; + string? CronExpression = null; if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase) { period = asyncPeriodicBackgroundWorkerBase.Period; - cornExpression = asyncPeriodicBackgroundWorkerBase.CornExpression; + CronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression; } else if (worker is PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase) { period = periodicBackgroundWorkerBase.Period; - cornExpression = periodicBackgroundWorkerBase.CornExpression; + CronExpression = periodicBackgroundWorkerBase.CronExpression; } - if (period == null && cornExpression.IsNullOrWhiteSpace()) + if (period == null && CronExpression.IsNullOrWhiteSpace()) { return; } @@ -53,9 +53,9 @@ public class QuartzPeriodicBackgroundWorkerAdapter : QuartzBackgroundWo .ForJob(JobDetail) .WithIdentity(BackgroundWorkerNameAttribute.GetName()); - if (!cornExpression.IsNullOrWhiteSpace()) + if (!CronExpression.IsNullOrWhiteSpace()) { - triggerBuilder.WithCronSchedule(cornExpression); + triggerBuilder.WithCronSchedule(CronExpression); } else { diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs index 938bd1d2a2..781d3535ba 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/AsyncPeriodicBackgroundWorkerBase.cs @@ -15,9 +15,9 @@ public abstract class AsyncPeriodicBackgroundWorkerBase : BackgroundWorkerBase protected CancellationToken StartCancellationToken { get; set; } public int Period => Timer.Period; /// - /// CornExpression has high priority over Period. + /// CronExpression has high priority over Period. /// - public string? CornExpression { get; protected set; } + public string? CronExpression { get; protected set; } protected AsyncPeriodicBackgroundWorkerBase( AbpAsyncTimer timer, diff --git a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs index 95fd8b342a..c7f9af7016 100644 --- a/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs +++ b/framework/src/Volo.Abp.BackgroundWorkers/Volo/Abp/BackgroundWorkers/PeriodicBackgroundWorkerBase.cs @@ -17,9 +17,9 @@ public abstract class PeriodicBackgroundWorkerBase : BackgroundWorkerBase protected AbpTimer Timer { get; } public int Period => Timer.Period; /// - /// CornExpression has high priority over Period. + /// CronExpression has high priority over Period. /// - public string? CornExpression { get; protected set; } + public string? CronExpression { get; protected set; } protected PeriodicBackgroundWorkerBase( AbpTimer timer,