diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 1300b5f4d75..86df2d87485 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -51,11 +51,14 @@ jobs:
timeout-minutes: 50
if: ${{ !github.event.pull_request.draft }}
steps:
+ - uses: jlumbroso/free-disk-space@main
+ - uses: PSModule/install-powershell@v1
+ with:
+ Version: latest
- uses: actions/checkout@v2
- uses: actions/setup-dotnet@master
with:
dotnet-version: 10.0.x
-
- name: Build All
run: ./build-all.ps1
working-directory: ./build
diff --git a/Directory.Packages.props b/Directory.Packages.props
index b2d490316cb..55b29df261f 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -182,6 +182,10 @@
+
+
+
+
diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json
index 62023f40876..4969fe54306 100644
--- a/docs/en/docs-nav.json
+++ b/docs/en/docs-nav.json
@@ -571,6 +571,10 @@
{
"text": "Quartz Integration",
"path": "framework/infrastructure/background-jobs/quartz.md"
+ },
+ {
+ "text": "TickerQ Integration",
+ "path": "framework/infrastructure/background-jobs/tickerq.md"
}
]
},
@@ -589,6 +593,10 @@
{
"text": "Hangfire Integration",
"path": "framework/infrastructure/background-workers/hangfire.md"
+ },
+ {
+ "text": "TickerQ Integration",
+ "path": "framework/infrastructure/background-workers/tickerq.md"
}
]
},
diff --git a/docs/en/framework/infrastructure/background-jobs/index.md b/docs/en/framework/infrastructure/background-jobs/index.md
index af0c33de217..3b5628d39f2 100644
--- a/docs/en/framework/infrastructure/background-jobs/index.md
+++ b/docs/en/framework/infrastructure/background-jobs/index.md
@@ -348,6 +348,7 @@ See pre-built job manager alternatives:
* [Hangfire Background Job Manager](./hangfire.md)
* [RabbitMQ Background Job Manager](./rabbitmq.md)
* [Quartz Background Job Manager](./quartz.md)
+* [TickerQ Background Job Manager](./tickerq.md)
## See Also
* [Background Workers](../background-workers)
\ No newline at end of file
diff --git a/docs/en/framework/infrastructure/background-jobs/tickerq.md b/docs/en/framework/infrastructure/background-jobs/tickerq.md
new file mode 100644
index 00000000000..013a8fde746
--- /dev/null
+++ b/docs/en/framework/infrastructure/background-jobs/tickerq.md
@@ -0,0 +1,125 @@
+# TickerQ Background Job Manager
+
+[TickerQ](https://tickerq.net/) is a fast, reflection-free background task scheduler for .NET — built with source generators, EF Core integration, cron + time-based execution, and a real-time dashboard. You can integrate TickerQ with the ABP to use it instead of the [default background job manager](../background-jobs). In this way, you can use the same background job API for TickerQ and your code will be independent of TickerQ. If you like, you can directly use TickerQ's API, too.
+
+> See the [background jobs document](../background-jobs) to learn how to use the background job system. This document only shows how to install and configure the TickerQ integration.
+
+## Installation
+
+It is suggested to use the [ABP CLI](../../../cli) to install this package.
+
+### Using the ABP CLI
+
+Open a command line window in the folder of the project (.csproj file) and type the following command:
+
+````bash
+abp add-package Volo.Abp.BackgroundJobs.TickerQ
+````
+
+> If you haven't done it yet, you first need to install the [ABP CLI](../../../cli). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundJobs.TickerQ).
+
+## Configuration
+
+### AddTickerQ
+
+You can call the `AddTickerQ` extension method in the `ConfigureServices` method of your module to configure TickerQ services:
+
+> This is optional. ABP will automatically register TickerQ services.
+
+```csharp
+public override void ConfigureServices(ServiceConfigurationContext context)
+{
+ context.Services.AddTickerQ(x =>
+ {
+ // Configure TickerQ options here
+ });
+}
+```
+
+### UseAbpTickerQ
+
+You need to call the `UseAbpTickerQ` extension method instead of `AddTickerQ` in the `OnApplicationInitialization` method of your module:
+
+```csharp
+// (default: TickerQStartMode.Immediate)
+app.UseAbpTickerQ(startMode: ...);
+```
+
+### AbpBackgroundJobsTickerQOptions
+
+You can configure the `TimeTicker` properties for specific jobs. For example, you can change `Priority`, `Retries` and `RetryIntervals` properties as shown below:
+
+```csharp
+Configure(options =>
+{
+ options.AddJobConfiguration(new AbpBackgroundJobsTimeTickerConfiguration()
+ {
+ Retries = 3,
+ RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min
+ Priority = TickerTaskPriority.High
+
+ // Optional batching
+ //BatchParent = Guid.Parse("...."),
+ //BatchRunCondition = BatchRunCondition.OnSuccess
+ });
+
+ options.AddJobConfiguration(new AbpBackgroundJobsTimeTickerConfiguration()
+ {
+ Retries = 5,
+ RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min
+ Priority = TickerTaskPriority.Normal
+ });
+});
+```
+
+### Add your own TickerQ Background Jobs Definitions
+
+ABP will handle the TickerQ job definitions by `AbpTickerQFunctionProvider` service. You shouldn't use `TickerFunction` to add your own job definitions. You can inject and use the `AbpTickerQFunctionProvider` to add your own definitions and use `ITimeTickerManager` or `ICronTickerManager` to manage the jobs.
+
+For example, you can add a `CleanupJobs` job definition in the `OnPreApplicationInitializationAsync` method of your module:
+
+```csharp
+public class CleanupJobs
+{
+ public async Task CleanupLogsAsync(TickerFunctionContext tickerContext, CancellationToken cancellationToken)
+ {
+ var logFileName = tickerContext.Request;
+ Console.WriteLine($"Cleaning up log file: {logFileName} at {DateTime.Now}");
+ }
+}
+```
+
+```csharp
+public override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
+{
+ var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService();
+ abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) =>
+ {
+ var service = new CleanupJobs(); // Or get it from the serviceProvider
+ var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type);
+ var genericContext = new TickerFunctionContext(tickerFunctionContext, request);
+ await service.CleanupLogsAsync(genericContext, cancellationToken);
+ })));
+ abpTickerQFunctionProvider.RequestTypes.TryAdd(nameof(CleanupJobs), (typeof(string).FullName, typeof(string)));
+ return Task.CompletedTask;
+}
+```
+
+And then you can add a job by using the `ITimeTickerManager`:
+
+```csharp
+var timeTickerManager = context.ServiceProvider.GetRequiredService>();
+await timeTickerManager.AddAsync(new TimeTicker
+{
+ Function = nameof(CleanupJobs),
+ ExecutionTime = DateTime.UtcNow.AddSeconds(5),
+ Request = TickerHelper.CreateTickerRequest("cleanup_example_file.txt"),
+ Retries = 3,
+ RetryIntervals = new[] { 30, 60, 120 }, // Retry after 30s, 60s, then 2min
+});
+```
+
+### TickerQ Dashboard and EF Core Integration
+
+You can install the [TickerQ dashboard](https://tickerq.net/setup/dashboard.html) and [Entity Framework Core](https://tickerq.net/setup/tickerq-ef-core.html) integration by its documentation. There is no specific configuration needed for the ABP integration.
+
diff --git a/docs/en/framework/infrastructure/background-workers/index.md b/docs/en/framework/infrastructure/background-workers/index.md
index 6ab88a9e534..498a52ed9bb 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 `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).
+> 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), [Quartz Background Worker Manager](./quartz.md), or [TickerQ Background Worker Manager](./tickerq.md).
````csharp
public class PassiveUserCheckerWorker : AsyncPeriodicBackgroundWorkerBase
@@ -216,7 +216,8 @@ Background worker system is extensible and you can change the default background
See pre-built worker manager alternatives:
* [Quartz Background Worker Manager](./quartz.md)
-* [Hangfire Background Worker Manager](./hangfire.md)
+* [Hangfire Background Worker Manager](./hangfire.md)
+* [TickerQ Background Worker Manager](./tickerq.md)
## See Also
diff --git a/docs/en/framework/infrastructure/background-workers/tickerq.md b/docs/en/framework/infrastructure/background-workers/tickerq.md
new file mode 100644
index 00000000000..840b5137cbb
--- /dev/null
+++ b/docs/en/framework/infrastructure/background-workers/tickerq.md
@@ -0,0 +1,119 @@
+# TickerQ Background Worker Manager
+
+[TickerQ](https://tickerq.net/) is a fast, reflection-free background task scheduler for .NET — built with source generators, EF Core integration, cron + time-based execution, and a real-time dashboard. You can integrate TickerQ with the ABP to use it instead of the [default background worker manager](../background-workers).
+
+## Installation
+
+It is suggested to use the [ABP CLI](../../../cli) to install this package.
+
+### Using the ABP CLI
+
+Open a command line window in the folder of the project (.csproj file) and type the following command:
+
+````bash
+abp add-package Volo.Abp.BackgroundWorkers.TickerQ
+````
+
+> If you haven't done it yet, you first need to install the [ABP CLI](../../../cli). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundWorkers.TickerQ).
+
+## Configuration
+
+### AddTickerQ
+
+You can call the `AddTickerQ` extension method in the `ConfigureServices` method of your module to configure TickerQ services:
+
+> This is optional. ABP will automatically register TickerQ services.
+
+```csharp
+public override void ConfigureServices(ServiceConfigurationContext context)
+{
+ context.Services.AddTickerQ(x =>
+ {
+ // Configure TickerQ options here
+ });
+}
+```
+
+### UseAbpTickerQ
+
+You need to call the `UseAbpTickerQ` extension method instead of `AddTickerQ` in the `OnApplicationInitialization` method of your module:
+
+```csharp
+// (default: TickerQStartMode.Immediate)
+app.UseAbpTickerQ(startMode: ...);
+```
+
+### AbpBackgroundWorkersTickerQOptions
+
+You can configure the `CronTicker` properties for specific jobs. For example, Change `Priority`, `Retries` and `RetryIntervals` properties:
+
+```csharp
+Configure(options =>
+{
+ options.AddConfiguration(new AbpBackgroundWorkersCronTickerConfiguration()
+ {
+ Retries = 3,
+ RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min,
+ Priority = TickerTaskPriority.High
+ });
+});
+```
+
+### Add your own TickerQ Background Worker Definitions
+
+ABP will handle the TickerQ job definitions by `AbpTickerQFunctionProvider` service. You shouldn't use `TickerFunction` to add your own job definitions. You can inject and use the `AbpTickerQFunctionProvider` to add your own definitions and use `ITimeTickerManager` or `ICronTickerManager` to manage the jobs.
+
+For example, you can add a `CleanupJobs` job definition in the `OnPreApplicationInitializationAsync` method of your module:
+
+```csharp
+public class CleanupJobs
+{
+ public async Task CleanupLogsAsync(TickerFunctionContext tickerContext, CancellationToken cancellationToken)
+ {
+ var logFileName = tickerContext.Request;
+ Console.WriteLine($"Cleaning up log file: {logFileName} at {DateTime.Now}");
+ }
+}
+```
+
+```csharp
+public override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
+{
+ var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService();
+ abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) =>
+ {
+ var service = new CleanupJobs(); // Or get it from the serviceProvider
+ var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type);
+ var genericContext = new TickerFunctionContext(tickerFunctionContext, request);
+ await service.CleanupLogsAsync(genericContext, cancellationToken);
+ })));
+ abpTickerQFunctionProvider.RequestTypes.TryAdd(nameof(CleanupJobs), (typeof(string).FullName, typeof(string)));
+ return Task.CompletedTask;
+}
+```
+
+And then you can add a job by using the `ICronTickerManager`:
+
+```csharp
+var cronTickerManager = context.ServiceProvider.GetRequiredService>();
+await cronTickerManager.AddAsync(new CronTicker
+{
+ Function = nameof(CleanupJobs),
+ Expression = "0 */6 * * *", // Every 6 hours
+ Request = TickerHelper.CreateTickerRequest("cleanup_example_file.txt"),
+ Retries = 2,
+ RetryIntervals = new[] { 60, 300 }
+});
+```
+
+You can specify a cron expression instead of use `ICronTickerManager` to add a worker:
+
+```csharp
+abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) =>
+{
+ var service = new CleanupJobs();
+ var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type);
+ var genericContext = new TickerFunctionContext(tickerFunctionContext, request);
+ await service.CleanupLogsAsync(genericContext, cancellationToken);
+})));
+```
diff --git a/framework/Volo.Abp.slnx b/framework/Volo.Abp.slnx
index ab63be61ead..f4c0d2b3739 100644
--- a/framework/Volo.Abp.slnx
+++ b/framework/Volo.Abp.slnx
@@ -166,6 +166,9 @@
+
+
+
@@ -252,4 +255,4 @@
-
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xml b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xml
new file mode 100644
index 00000000000..1715698ccd2
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xsd b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xsd
new file mode 100644
index 00000000000..ffa6fc4b782
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj
new file mode 100644
index 00000000000..df450f5ff6d
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ netstandard2.1;net8.0;net9.0;net10.0
+ enable
+ Nullable
+ Volo.Abp.BackgroundJobs.TickerQ
+ Volo.Abp.BackgroundJobs.TickerQ
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs
new file mode 100644
index 00000000000..b5ed5989324
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using TickerQ.Utilities;
+using TickerQ.Utilities.Enums;
+using Volo.Abp.Modularity;
+using Volo.Abp.TickerQ;
+
+namespace Volo.Abp.BackgroundJobs.TickerQ;
+
+[DependsOn(
+ typeof(AbpBackgroundJobsAbstractionsModule),
+ typeof(AbpTickerQModule)
+)]
+public class AbpBackgroundJobsTickerQModule : AbpModule
+{
+ private static readonly MethodInfo GetTickerFunctionDelegateMethod =
+ typeof(AbpBackgroundJobsTickerQModule).GetMethod(nameof(GetTickerFunctionDelegate), BindingFlags.NonPublic | BindingFlags.Static)!;
+
+ public override void OnApplicationInitialization(ApplicationInitializationContext context)
+ {
+ var abpBackgroundJobOptions = context.ServiceProvider.GetRequiredService>();
+ var abpBackgroundJobsTickerQOptions = context.ServiceProvider.GetRequiredService>();
+ var tickerFunctionDelegates = new Dictionary();
+ var requestTypes = new Dictionary();
+ foreach (var jobConfiguration in abpBackgroundJobOptions.Value.GetJobs())
+ {
+ var genericMethod = GetTickerFunctionDelegateMethod.MakeGenericMethod(jobConfiguration.ArgsType);
+ var tickerFunctionDelegate = (TickerFunctionDelegate)genericMethod.Invoke(null, [jobConfiguration.ArgsType])!;
+ var config = abpBackgroundJobsTickerQOptions.Value.GetConfigurationOrNull(jobConfiguration.JobType);
+ tickerFunctionDelegates.TryAdd(jobConfiguration.JobName, (string.Empty, config?.Priority ?? TickerTaskPriority.Normal, tickerFunctionDelegate));
+ requestTypes.TryAdd(jobConfiguration.JobName, (jobConfiguration.ArgsType.FullName, jobConfiguration.ArgsType)!);
+ }
+
+ var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService();
+ foreach (var functionDelegate in tickerFunctionDelegates)
+ {
+ abpTickerQFunctionProvider.Functions.TryAdd(functionDelegate.Key, functionDelegate.Value);
+ }
+
+ foreach (var requestType in requestTypes)
+ {
+ abpTickerQFunctionProvider.RequestTypes.TryAdd(requestType.Key, requestType.Value);
+ }
+ }
+
+ private static TickerFunctionDelegate GetTickerFunctionDelegate(Type argsType)
+ {
+ return async (cancellationToken, serviceProvider, context) =>
+ {
+ var options = serviceProvider.GetRequiredService>().Value;
+ if (!options.IsJobExecutionEnabled)
+ {
+ throw new AbpException(
+ "Background job execution is disabled. " +
+ "This method should not be called! " +
+ "If you want to enable the background job execution, " +
+ $"set {nameof(AbpBackgroundJobOptions)}.{nameof(AbpBackgroundJobOptions.IsJobExecutionEnabled)} to true! " +
+ "If you've intentionally disabled job execution and this seems a bug, please report it."
+ );
+ }
+
+ using (var scope = serviceProvider.CreateScope())
+ {
+ var jobExecuter = serviceProvider.GetRequiredService();
+ var args = await TickerRequestProvider.GetRequestAsync(serviceProvider, context.Id, context.Type);
+ var jobType = options.GetJob(typeof(TArgs)).JobType;
+ var jobExecutionContext = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: cancellationToken);
+ await jobExecuter.ExecuteAsync(jobExecutionContext);
+ }
+ };
+ }
+}
diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQOptions.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQOptions.cs
new file mode 100644
index 00000000000..f85b3e5feff
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQOptions.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+
+namespace Volo.Abp.BackgroundJobs.TickerQ;
+
+public class AbpBackgroundJobsTickerQOptions
+{
+ private readonly Dictionary _configurations;
+
+ public AbpBackgroundJobsTickerQOptions()
+ {
+ _configurations = new Dictionary();
+ }
+
+ public void AddConfiguration(AbpBackgroundJobsTimeTickerConfiguration configuration)
+ {
+ AddConfiguration(typeof(TJob), configuration);
+ }
+
+ public void AddConfiguration(Type jobType, AbpBackgroundJobsTimeTickerConfiguration configuration)
+ {
+ _configurations[jobType] = configuration;
+ }
+
+ public AbpBackgroundJobsTimeTickerConfiguration? GetConfigurationOrNull()
+ {
+ return GetConfigurationOrNull(typeof(TJob));
+ }
+
+ public AbpBackgroundJobsTimeTickerConfiguration? GetConfigurationOrNull(Type jobType)
+ {
+ return _configurations.GetValueOrDefault(jobType);
+ }
+}
diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs
new file mode 100644
index 00000000000..65b150ea483
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs
@@ -0,0 +1,17 @@
+using System;
+using TickerQ.Utilities.Enums;
+
+namespace Volo.Abp.BackgroundJobs.TickerQ;
+
+public class AbpBackgroundJobsTimeTickerConfiguration
+{
+ public int? Retries { get; set; }
+
+ public int[]? RetryIntervals { get; set; }
+
+ public TickerTaskPriority? Priority { get; set; }
+
+ public Guid? BatchParent { get; set; }
+
+ public BatchRunCondition? BatchRunCondition { get; set; }
+}
diff --git a/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs
new file mode 100644
index 00000000000..b1a2dbe36dc
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+using TickerQ.Utilities;
+using TickerQ.Utilities.Interfaces.Managers;
+using TickerQ.Utilities.Models.Ticker;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.BackgroundJobs.TickerQ;
+
+[Dependency(ReplaceServices = true)]
+public class AbpTickerQBackgroundJobManager : IBackgroundJobManager, ITransientDependency
+{
+ protected ITimeTickerManager TimeTickerManager { get; }
+ protected AbpBackgroundJobOptions Options { get; }
+ protected AbpBackgroundJobsTickerQOptions TickerQOptions { get; }
+
+ public AbpTickerQBackgroundJobManager(
+ ITimeTickerManager timeTickerManager,
+ IOptions options,
+ IOptions tickerQOptions)
+ {
+ TimeTickerManager = timeTickerManager;
+ Options = options.Value;
+ TickerQOptions = tickerQOptions.Value;
+ }
+
+ public virtual async Task EnqueueAsync(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null)
+ {
+ var job = Options.GetJob(typeof(TArgs));
+ var timeTicker = new TimeTicker
+ {
+ Id = Guid.NewGuid(),
+ Function = job.JobName,
+ ExecutionTime = delay == null ? DateTime.UtcNow : DateTime.UtcNow.Add(delay.Value),
+ Request = TickerHelper.CreateTickerRequest(args),
+ };
+
+ var config = TickerQOptions.GetConfigurationOrNull(job.JobType);
+ if (config != null)
+ {
+ timeTicker.Retries = config.Retries ?? timeTicker.Retries;
+ timeTicker.RetryIntervals = config.RetryIntervals ?? timeTicker.RetryIntervals;
+ timeTicker.BatchParent = config.BatchParent ?? timeTicker.BatchParent;
+ timeTicker.BatchRunCondition = config.BatchRunCondition ?? timeTicker.BatchRunCondition;
+ }
+
+ var result = await TimeTickerManager.AddAsync(timeTicker);
+ return !result.IsSucceded ? timeTicker.Id.ToString() : result.Result.Id.ToString();
+ }
+}
diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xml b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xml
new file mode 100644
index 00000000000..1715698ccd2
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xsd b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xsd
new file mode 100644
index 00000000000..ffa6fc4b782
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj
new file mode 100644
index 00000000000..2c4ab29c97e
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+ netstandard2.1;net8.0;net9.0;net10.0
+ enable
+ Nullable
+ Volo.Abp.BackgroundWorkers.TickerQ
+ Volo.Abp.BackgroundWorkers.TickerQ
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersCronTickerConfiguration.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersCronTickerConfiguration.cs
new file mode 100644
index 00000000000..0e8ed89a145
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersCronTickerConfiguration.cs
@@ -0,0 +1,12 @@
+using TickerQ.Utilities.Enums;
+
+namespace Volo.Abp.BackgroundWorkers.TickerQ;
+
+public class AbpBackgroundWorkersCronTickerConfiguration
+{
+ public int? Retries { get; set; }
+
+ public int[]? RetryIntervals { get; set; }
+
+ public TickerTaskPriority? Priority { get; set; }
+}
diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs
new file mode 100644
index 00000000000..3fb15a50b83
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs
@@ -0,0 +1,37 @@
+using System.Threading.Tasks;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using TickerQ.Utilities.Interfaces.Managers;
+using TickerQ.Utilities.Models.Ticker;
+using Volo.Abp.Modularity;
+using Volo.Abp.TickerQ;
+
+namespace Volo.Abp.BackgroundWorkers.TickerQ;
+
+[DependsOn(typeof(AbpBackgroundWorkersModule), typeof(AbpTickerQModule))]
+public class AbpBackgroundWorkersTickerQModule : AbpModule
+{
+ public override async Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context)
+ {
+ var abpTickerQBackgroundWorkersProvider = context.ServiceProvider.GetRequiredService();
+ var cronTickerManager = context.ServiceProvider.GetRequiredService>();
+ var abpBackgroundWorkersTickerQOptions = context.ServiceProvider.GetRequiredService>().Value;
+ foreach (var backgroundWorker in abpTickerQBackgroundWorkersProvider.BackgroundWorkers)
+ {
+ var cronTicker = new CronTicker
+ {
+ Function = backgroundWorker.Value.Function,
+ Expression = backgroundWorker.Value.CronExpression
+ };
+
+ var config = abpBackgroundWorkersTickerQOptions.GetConfigurationOrNull(backgroundWorker.Value.WorkerType);
+ if (config != null)
+ {
+ cronTicker.Retries = config.Retries ?? cronTicker.Retries;
+ cronTicker.RetryIntervals = config.RetryIntervals ?? cronTicker.RetryIntervals;
+ }
+
+ await cronTickerManager.AddAsync(cronTicker);
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs
new file mode 100644
index 00000000000..6d48a212626
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs
@@ -0,0 +1,34 @@
+using System;
+using System.Collections.Generic;
+
+namespace Volo.Abp.BackgroundWorkers.TickerQ;
+
+public class AbpBackgroundWorkersTickerQOptions
+{
+ private readonly Dictionary _onfigurations;
+
+ public AbpBackgroundWorkersTickerQOptions()
+ {
+ _onfigurations = new Dictionary();
+ }
+
+ public void AddConfiguration(AbpBackgroundWorkersCronTickerConfiguration configuration)
+ {
+ AddConfiguration(typeof(TWorker), configuration);
+ }
+
+ public void AddConfiguration(Type workerType, AbpBackgroundWorkersCronTickerConfiguration configuration)
+ {
+ _onfigurations[workerType] = configuration;
+ }
+
+ public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull()
+ {
+ return GetConfigurationOrNull(typeof(TJob));
+ }
+
+ public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull(Type workerType)
+ {
+ return _onfigurations.GetValueOrDefault(workerType);
+ }
+}
diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkerManager.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkerManager.cs
new file mode 100644
index 00000000000..922cad294dd
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkerManager.cs
@@ -0,0 +1,105 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+using TickerQ.Utilities.Enums;
+using Volo.Abp.DependencyInjection;
+using Volo.Abp.DynamicProxy;
+using Volo.Abp.TickerQ;
+
+namespace Volo.Abp.BackgroundWorkers.TickerQ;
+
+[Dependency(ReplaceServices = true)]
+public class AbpTickerQBackgroundWorkerManager : BackgroundWorkerManager, ISingletonDependency
+{
+ protected AbpTickerQFunctionProvider AbpTickerQFunctionProvider { get; }
+ protected AbpTickerQBackgroundWorkersProvider AbpTickerQBackgroundWorkersProvider { get; }
+ protected AbpBackgroundWorkersTickerQOptions Options { get; }
+
+ public AbpTickerQBackgroundWorkerManager(
+ AbpTickerQFunctionProvider abpTickerQFunctionProvider,
+ AbpTickerQBackgroundWorkersProvider abpTickerQBackgroundWorkersProvider,
+ IOptions options)
+ {
+ AbpTickerQFunctionProvider = abpTickerQFunctionProvider;
+ AbpTickerQBackgroundWorkersProvider = abpTickerQBackgroundWorkersProvider;
+ Options = options.Value;
+ }
+
+ public override async Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default)
+ {
+ if (worker is AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase)
+ {
+ int? period = null;
+ string? cronExpression = null;
+
+ if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase)
+ {
+ period = asyncPeriodicBackgroundWorkerBase.Period;
+ cronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression;
+ }
+ else if (worker is PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase)
+ {
+ period = periodicBackgroundWorkerBase.Period;
+ cronExpression = periodicBackgroundWorkerBase.CronExpression;
+ }
+
+ if (period == null && cronExpression.IsNullOrWhiteSpace())
+ {
+ throw new AbpException($"Both 'Period' and 'CronExpression' are not set for {worker.GetType().FullName}. You must set at least one of them.");
+ }
+
+ cronExpression = cronExpression ?? GetCron(period!.Value);
+ var name = BackgroundWorkerNameAttribute.GetNameOrNull(worker.GetType()) ?? worker.GetType().FullName;
+
+ var config = Options.GetConfigurationOrNull(ProxyHelper.GetUnProxiedType(worker));
+ AbpTickerQFunctionProvider.Functions.TryAdd(name!, (string.Empty, config?.Priority ?? TickerTaskPriority.LongRunning, async (tickerQCancellationToken, serviceProvider, tickerFunctionContext) =>
+ {
+ var workerInvoker = new AbpTickerQPeriodicBackgroundWorkerInvoker(worker, serviceProvider);
+ await workerInvoker.DoWorkAsync(tickerFunctionContext, tickerQCancellationToken);
+ }));
+
+ AbpTickerQBackgroundWorkersProvider.BackgroundWorkers.Add(name!, new AbpTickerQCronBackgroundWorker
+ {
+ Function = name!,
+ CronExpression = cronExpression,
+ WorkerType = ProxyHelper.GetUnProxiedType(worker)
+ });
+ }
+
+ await base.AddAsync(worker, cancellationToken);
+ }
+
+ protected virtual string GetCron(int period)
+ {
+ var time = TimeSpan.FromMilliseconds(period);
+ if (time.TotalMinutes < 1)
+ {
+ // Less than 1 minute — 5-field cron doesn't support seconds, so run every minute
+ return "* * * * *";
+ }
+
+ if (time.TotalMinutes < 60)
+ {
+ // Run every N minutes
+ var minutes = (int)Math.Round(time.TotalMinutes);
+ return $"*/{minutes} * * * *";
+ }
+
+ if (time.TotalHours < 24)
+ {
+ // Run every N hours
+ var hours = (int)Math.Round(time.TotalHours);
+ return $"0 */{hours} * * *";
+ }
+
+ if (time.TotalDays <= 31)
+ {
+ // Run every N days
+ var days = (int)Math.Round(time.TotalDays);
+ return $"0 0 */{days} * *";
+ }
+
+ throw new AbpException($"Cannot convert period: {period} to cron expression.");
+ }
+}
diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkersProvider.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkersProvider.cs
new file mode 100644
index 00000000000..3c0fe763ece
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkersProvider.cs
@@ -0,0 +1,14 @@
+using System.Collections.Generic;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.BackgroundWorkers.TickerQ;
+
+public class AbpTickerQBackgroundWorkersProvider : ISingletonDependency
+{
+ public Dictionary BackgroundWorkers { get;}
+
+ public AbpTickerQBackgroundWorkersProvider()
+ {
+ BackgroundWorkers = new Dictionary();
+ }
+}
diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQCronBackgroundWorker.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQCronBackgroundWorker.cs
new file mode 100644
index 00000000000..55c97a00a6a
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQCronBackgroundWorker.cs
@@ -0,0 +1,12 @@
+using System;
+
+namespace Volo.Abp.BackgroundWorkers.TickerQ;
+
+public class AbpTickerQCronBackgroundWorker
+{
+ public string Function { get; set; } = null!;
+
+ public string CronExpression { get; set; } = null!;
+
+ public Type WorkerType { get; set; } = null!;
+}
diff --git a/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs
new file mode 100644
index 00000000000..17cf7cdc874
--- /dev/null
+++ b/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Linq.Expressions;
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+using TickerQ.Utilities.Models;
+
+namespace Volo.Abp.BackgroundWorkers.TickerQ;
+
+public class AbpTickerQPeriodicBackgroundWorkerInvoker
+{
+ private readonly Func? _doWorkAsyncDelegate;
+ private readonly Action? _doWorkDelegate;
+
+ protected IBackgroundWorker Worker { get; }
+ protected IServiceProvider ServiceProvider { get; }
+
+ public AbpTickerQPeriodicBackgroundWorkerInvoker(IBackgroundWorker worker, IServiceProvider serviceProvider)
+ {
+ Worker = worker;
+ ServiceProvider = serviceProvider;
+
+ switch (worker)
+ {
+ case AsyncPeriodicBackgroundWorkerBase:
+ {
+ var workerType = worker.GetType();
+ var method = workerType.GetMethod("DoWorkAsync", BindingFlags.Instance | BindingFlags.NonPublic);
+ if (method == null)
+ {
+ throw new AbpException($"Could not find 'DoWorkAsync' method on type '{workerType.FullName}'.");
+ }
+
+ var instanceParam = Expression.Parameter(typeof(AsyncPeriodicBackgroundWorkerBase), "worker");
+ var contextParam = Expression.Parameter(typeof(PeriodicBackgroundWorkerContext), "context");
+ var call = Expression.Call(Expression.Convert(instanceParam, workerType), method, contextParam);
+ var lambda = Expression.Lambda>(call, instanceParam, contextParam);
+ _doWorkAsyncDelegate = lambda.Compile();
+ break;
+ }
+ case PeriodicBackgroundWorkerBase:
+ {
+ var workerType = worker.GetType();
+ var method = workerType.GetMethod("DoWork", BindingFlags.Instance | BindingFlags.NonPublic);
+ if (method == null)
+ {
+ throw new AbpException($"Could not find 'DoWork' method on type '{workerType.FullName}'.");
+ }
+
+ var instanceParam = Expression.Parameter(typeof(PeriodicBackgroundWorkerBase), "worker");
+ var contextParam = Expression.Parameter(typeof(PeriodicBackgroundWorkerContext), "context");
+ var call = Expression.Call(Expression.Convert(instanceParam, workerType), method, contextParam);
+ var lambda = Expression.Lambda>(call, instanceParam, contextParam);
+ _doWorkDelegate = lambda.Compile();
+ break;
+ }
+ }
+ }
+
+ public virtual async Task DoWorkAsync(TickerFunctionContext context, CancellationToken cancellationToken = default)
+ {
+ var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider);
+ switch (Worker)
+ {
+ case AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorker:
+ await _doWorkAsyncDelegate!(asyncPeriodicBackgroundWorker, workerContext);
+ break;
+ case PeriodicBackgroundWorkerBase periodicBackgroundWorker:
+ _doWorkDelegate!(periodicBackgroundWorker, workerContext);
+ break;
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.TickerQ/FodyWeavers.xml b/framework/src/Volo.Abp.TickerQ/FodyWeavers.xml
new file mode 100644
index 00000000000..1715698ccd2
--- /dev/null
+++ b/framework/src/Volo.Abp.TickerQ/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.TickerQ/FodyWeavers.xsd b/framework/src/Volo.Abp.TickerQ/FodyWeavers.xsd
new file mode 100644
index 00000000000..ffa6fc4b782
--- /dev/null
+++ b/framework/src/Volo.Abp.TickerQ/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.TickerQ/Microsoft/AspNetCore/Builder/AbpTickerQApplicationBuilderExtensions.cs b/framework/src/Volo.Abp.TickerQ/Microsoft/AspNetCore/Builder/AbpTickerQApplicationBuilderExtensions.cs
new file mode 100644
index 00000000000..4e81565e992
--- /dev/null
+++ b/framework/src/Volo.Abp.TickerQ/Microsoft/AspNetCore/Builder/AbpTickerQApplicationBuilderExtensions.cs
@@ -0,0 +1,20 @@
+using Microsoft.Extensions.DependencyInjection;
+using TickerQ.DependencyInjection;
+using TickerQ.Utilities;
+using TickerQ.Utilities.Enums;
+using Volo.Abp.TickerQ;
+
+namespace Microsoft.AspNetCore.Builder;
+
+public static class AbpTickerQApplicationBuilderExtensions
+{
+ public static IApplicationBuilder UseAbpTickerQ(this IApplicationBuilder app, TickerQStartMode qStartMode = TickerQStartMode.Immediate)
+ {
+ var abpTickerQFunctionProvider = app.ApplicationServices.GetRequiredService();
+ TickerFunctionProvider.RegisterFunctions(abpTickerQFunctionProvider.Functions);
+ TickerFunctionProvider.RegisterRequestType(abpTickerQFunctionProvider.RequestTypes);
+
+ app.UseTickerQ(qStartMode);
+ return app;
+ }
+}
diff --git a/framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj b/framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj
new file mode 100644
index 00000000000..89a037bab76
--- /dev/null
+++ b/framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+ netstandard2.1;net8.0;net9.0;net10.0
+ enable
+ Nullable
+ Volo.Abp.TickerQ
+ Volo.Abp.TickerQ
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQFunctionProvider.cs b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQFunctionProvider.cs
new file mode 100644
index 00000000000..b92888e5338
--- /dev/null
+++ b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQFunctionProvider.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using TickerQ.Utilities;
+using TickerQ.Utilities.Enums;
+using Volo.Abp.DependencyInjection;
+
+namespace Volo.Abp.TickerQ;
+
+public class AbpTickerQFunctionProvider : ISingletonDependency
+{
+ public Dictionary Functions { get;}
+
+ public Dictionary RequestTypes { get; }
+
+ public AbpTickerQFunctionProvider()
+ {
+ Functions = new Dictionary();
+ RequestTypes = new Dictionary();
+ }
+}
diff --git a/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs
new file mode 100644
index 00000000000..b6c140e9622
--- /dev/null
+++ b/framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs
@@ -0,0 +1,16 @@
+using Microsoft.Extensions.DependencyInjection;
+using TickerQ.DependencyInjection;
+using Volo.Abp.Modularity;
+
+namespace Volo.Abp.TickerQ;
+
+public class AbpTickerQModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddTickerQ(options =>
+ {
+ options.SetInstanceIdentifier(context.Services.GetApplicationName());
+ });
+ }
+}
diff --git a/modules/background-jobs/Volo.Abp.BackgroundJobs.slnx b/modules/background-jobs/Volo.Abp.BackgroundJobs.slnx
index d8a41ccb9d9..9863504e7cd 100644
--- a/modules/background-jobs/Volo.Abp.BackgroundJobs.slnx
+++ b/modules/background-jobs/Volo.Abp.BackgroundJobs.slnx
@@ -5,6 +5,7 @@
+
@@ -19,4 +20,4 @@
-
+
\ No newline at end of file
diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs
index ad002a5e186..7e67dd48a99 100644
--- a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs
+++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs
@@ -4,9 +4,6 @@
namespace Volo.Abp.BackgroundJobs.DemoApp.Shared
{
- [DependsOn(
- typeof(AbpBackgroundJobsModule)
- )]
public class DemoAppSharedModule : AbpModule
{
public override void OnPostApplicationInitialization(ApplicationInitializationContext context)
diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs
new file mode 100644
index 00000000000..6eca55e09f4
--- /dev/null
+++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs
@@ -0,0 +1,15 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using TickerQ.Utilities.Models;
+
+namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ;
+
+public class CleanupJobs
+{
+ public async Task CleanupLogsAsync(TickerFunctionContext tickerContext, CancellationToken cancellationToken)
+ {
+ var logFileName = tickerContext.Request;
+ Console.WriteLine($"Cleaning up log file: {logFileName} at {DateTime.Now}");
+ }
+}
diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs
new file mode 100644
index 00000000000..c9a3b957dee
--- /dev/null
+++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs
@@ -0,0 +1,140 @@
+using System;
+using System.Collections.Generic;
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using TickerQ.Dashboard.DependencyInjection;
+using TickerQ.DependencyInjection;
+using TickerQ.Utilities;
+using TickerQ.Utilities.Enums;
+using TickerQ.Utilities.Interfaces.Managers;
+using TickerQ.Utilities.Models;
+using TickerQ.Utilities.Models.Ticker;
+using Volo.Abp.AspNetCore;
+using Volo.Abp.Autofac;
+using Volo.Abp.BackgroundJobs.DemoApp.Shared;
+using Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs;
+using Volo.Abp.BackgroundJobs.TickerQ;
+using Volo.Abp.BackgroundWorkers;
+using Volo.Abp.BackgroundWorkers.TickerQ;
+using Volo.Abp.Modularity;
+using Volo.Abp.TickerQ;
+
+namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ;
+
+[DependsOn(
+ typeof(AbpBackgroundJobsTickerQModule),
+ typeof(AbpBackgroundWorkersTickerQModule),
+ typeof(DemoAppSharedModule),
+ typeof(AbpAutofacModule),
+ typeof(AbpAspNetCoreModule)
+)]
+public class DemoAppTickerQModule : AbpModule
+{
+ public override void ConfigureServices(ServiceConfigurationContext context)
+ {
+ context.Services.AddTickerQ(options =>
+ {
+ options.UpdateMissedJobCheckDelay(TimeSpan.FromSeconds(30));
+
+ options.AddDashboard(x =>
+ {
+ x.BasePath = "/tickerq-dashboard";
+
+ x.UseHostAuthentication = true;
+ });
+ });
+
+ Configure(options =>
+ {
+ options.AddConfiguration(new AbpBackgroundJobsTimeTickerConfiguration()
+ {
+ Retries = 3,
+ RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min,
+ Priority = TickerTaskPriority.High
+ });
+
+ options.AddConfiguration(new AbpBackgroundJobsTimeTickerConfiguration()
+ {
+ Retries = 5,
+ RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min
+ });
+ });
+
+ Configure(options =>
+ {
+ options.AddConfiguration(new AbpBackgroundWorkersCronTickerConfiguration()
+ {
+ Retries = 3,
+ RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min,
+ Priority = TickerTaskPriority.High
+ });
+ });
+ }
+
+ public override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context)
+ {
+ var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService();
+ abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) =>
+ {
+ var service = new CleanupJobs();
+ var request = await TickerRequestProvider.GetRequestAsync(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type);
+ var genericContext = new TickerFunctionContext(tickerFunctionContext, request);
+ await service.CleanupLogsAsync(genericContext, cancellationToken);
+ })));
+ abpTickerQFunctionProvider.RequestTypes.TryAdd(nameof(CleanupJobs), (typeof(string).FullName, typeof(string)));
+ return Task.CompletedTask;
+ }
+
+ public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
+ {
+ var backgroundWorkerManager = context.ServiceProvider.GetRequiredService();
+ await backgroundWorkerManager.AddAsync(context.ServiceProvider.GetRequiredService());
+
+ var app = context.GetApplicationBuilder();
+ app.UseAbpTickerQ();
+
+ var timeTickerManager = context.ServiceProvider.GetRequiredService>();
+ await timeTickerManager.AddAsync(new TimeTicker
+ {
+ Function = nameof(CleanupJobs),
+ ExecutionTime = DateTime.UtcNow.AddSeconds(5),
+ Request = TickerHelper.CreateTickerRequest("cleanup_example_file.txt"),
+ Retries = 3,
+ RetryIntervals = new[] { 30, 60, 120 }, // Retry after 30s, 60s, then 2min
+ });
+
+ var cronTickerManager = context.ServiceProvider.GetRequiredService>();
+ await cronTickerManager.AddAsync(new CronTicker
+ {
+ Function = nameof(CleanupJobs),
+ Expression = "* * * * *", // Every minute
+ Request = TickerHelper.CreateTickerRequest("cleanup_example_file.txt"),
+ Retries = 2,
+ RetryIntervals = new[] { 60, 300 }
+ });
+
+ app.UseRouting();
+ app.UseEndpoints(endpoints =>
+ {
+ endpoints.MapGet("/", async httpContext =>
+ {
+ httpContext.Response.Redirect("/tickerq-dashboard", true);
+ });
+ });
+
+ await CancelableBackgroundJobAsync(context.ServiceProvider);
+ }
+
+ private async Task CancelableBackgroundJobAsync(IServiceProvider serviceProvider)
+ {
+ var backgroundJobManager = serviceProvider.GetRequiredService();
+ var jobId = await backgroundJobManager.EnqueueAsync(new LongRunningJobArgs { Value = "test-cancel-job" });
+ await backgroundJobManager.EnqueueAsync(new LongRunningJobArgs { Value = "test-3" });
+
+ await Task.Delay(1000);
+
+ var timeTickerManager = serviceProvider.GetRequiredService>();
+ var result = await timeTickerManager.DeleteAsync(Guid.Parse(jobId));
+ }
+}
diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/MyBackgroundWorker.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/MyBackgroundWorker.cs
new file mode 100644
index 00000000000..cd35b42bb4d
--- /dev/null
+++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/MyBackgroundWorker.cs
@@ -0,0 +1,22 @@
+using System;
+using System.Threading.Tasks;
+using JetBrains.Annotations;
+using Microsoft.Extensions.DependencyInjection;
+using Volo.Abp.BackgroundWorkers;
+using Volo.Abp.Threading;
+
+namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ;
+
+public class MyBackgroundWorker : AsyncPeriodicBackgroundWorkerBase
+{
+ public MyBackgroundWorker([NotNull] AbpAsyncTimer timer, [NotNull] IServiceScopeFactory serviceScopeFactory) : base(timer, serviceScopeFactory)
+ {
+ timer.Period = 60 * 1000; // 60 seconds
+ CronExpression = "* * * * *"; // every minute
+ }
+
+ protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
+ {
+ Console.WriteLine($"MyBackgroundWorker executed at {DateTime.Now}");
+ }
+}
diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Program.cs b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Program.cs
new file mode 100644
index 00000000000..72e7f9090c0
--- /dev/null
+++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Program.cs
@@ -0,0 +1,19 @@
+using System.Threading.Tasks;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+
+namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ;
+
+public class Program
+{
+ public static async Task Main(string[] args)
+ {
+ var builder = WebApplication.CreateBuilder(args);
+ builder.Host.UseAutofac();
+ await builder.AddApplicationAsync();
+ var app = builder.Build();
+ await app.InitializeApplicationAsync();
+ await app.RunAsync();
+ }
+}
diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Properties/launchSettings.json b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Properties/launchSettings.json
new file mode 100644
index 00000000000..1b8cdbbc25e
--- /dev/null
+++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Properties/launchSettings.json
@@ -0,0 +1,13 @@
+{
+ "$schema": "http://json.schemastore.org/launchsettings.json",
+ "profiles": {
+ "Volo.Abp.BackgroundJobs.DemoApp.TickerQ": {
+ "commandName": "Project",
+ "launchBrowser": true,
+ "applicationUrl": "https://localhost:5000",
+ "environmentVariables": {
+ "ASPNETCORE_ENVIRONMENT": "Development"
+ }
+ }
+ }
+}
diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Volo.Abp.BackgroundJobs.DemoApp.TickerQ.csproj b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Volo.Abp.BackgroundJobs.DemoApp.TickerQ.csproj
new file mode 100644
index 00000000000..f30eba83dda
--- /dev/null
+++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Volo.Abp.BackgroundJobs.DemoApp.TickerQ.csproj
@@ -0,0 +1,26 @@
+
+
+
+ Exe
+ net10.0
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+
+
diff --git a/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/appsettings.json b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/appsettings.json
new file mode 100644
index 00000000000..1b2d3bafd88
--- /dev/null
+++ b/modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/appsettings.json
@@ -0,0 +1,8 @@
+{
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft.AspNetCore": "Warning"
+ }
+ }
+}
\ No newline at end of file
diff --git a/nupkg/common.ps1 b/nupkg/common.ps1
index 20b08ac2acd..6fbc34e80c3 100644
--- a/nupkg/common.ps1
+++ b/nupkg/common.ps1
@@ -148,9 +148,11 @@ $projects = (
"framework/src/Volo.Abp.BackgroundJobs.HangFire",
"framework/src/Volo.Abp.BackgroundJobs.RabbitMQ",
"framework/src/Volo.Abp.BackgroundJobs.Quartz",
+ "framework/src/Volo.Abp.BackgroundJobs.TickerQ",
"framework/src/Volo.Abp.BackgroundWorkers",
"framework/src/Volo.Abp.BackgroundWorkers.Quartz",
"framework/src/Volo.Abp.BackgroundWorkers.Hangfire",
+ "framework/src/Volo.Abp.BackgroundWorkers.TickerQ",
"framework/src/Volo.Abp.BlazoriseUI",
"framework/src/Volo.Abp.BlobStoring",
"framework/src/Volo.Abp.BlobStoring.FileSystem",
@@ -201,6 +203,7 @@ $projects = (
"framework/src/Volo.Abp.GlobalFeatures",
"framework/src/Volo.Abp.Guids",
"framework/src/Volo.Abp.HangFire",
+ "framework/src/Volo.Abp.TickerQ",
"framework/src/Volo.Abp.Http.Abstractions",
"framework/src/Volo.Abp.Http.Client",
"framework/src/Volo.Abp.Http.Client.Dapr",