Browse Source

Merge pull request #23802 from abpframework/copilot/fix-d5496b38-0efc-4016-8ab2-970b67675314

Add TickerQ Background Worker Integration
pull/23904/head
Ma Liming 10 months ago
committed by GitHub
parent
commit
98ff26db45
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      .github/workflows/build-and-test.yml
  2. 4
      Directory.Packages.props
  3. 8
      docs/en/docs-nav.json
  4. 1
      docs/en/framework/infrastructure/background-jobs/index.md
  5. 125
      docs/en/framework/infrastructure/background-jobs/tickerq.md
  6. 5
      docs/en/framework/infrastructure/background-workers/index.md
  7. 119
      docs/en/framework/infrastructure/background-workers/tickerq.md
  8. 5
      framework/Volo.Abp.slnx
  9. 3
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xml
  10. 30
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xsd
  11. 24
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj
  12. 75
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs
  13. 34
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQOptions.cs
  14. 17
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTimeTickerConfiguration.cs
  15. 51
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpTickerQBackgroundJobManager.cs
  16. 3
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xml
  17. 30
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xsd
  18. 24
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj
  19. 12
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersCronTickerConfiguration.cs
  20. 37
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs
  21. 34
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQOptions.cs
  22. 105
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkerManager.cs
  23. 14
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkersProvider.cs
  24. 12
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQCronBackgroundWorker.cs
  25. 73
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQPeriodicBackgroundWorkerInvoker.cs
  26. 3
      framework/src/Volo.Abp.TickerQ/FodyWeavers.xml
  27. 30
      framework/src/Volo.Abp.TickerQ/FodyWeavers.xsd
  28. 20
      framework/src/Volo.Abp.TickerQ/Microsoft/AspNetCore/Builder/AbpTickerQApplicationBuilderExtensions.cs
  29. 27
      framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj
  30. 20
      framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQFunctionProvider.cs
  31. 16
      framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs
  32. 3
      modules/background-jobs/Volo.Abp.BackgroundJobs.slnx
  33. 3
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs
  34. 15
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/CleanupJobs.cs
  35. 140
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/DemoAppTickerQModule.cs
  36. 22
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/MyBackgroundWorker.cs
  37. 19
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Program.cs
  38. 13
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Properties/launchSettings.json
  39. 26
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Volo.Abp.BackgroundJobs.DemoApp.TickerQ.csproj
  40. 8
      modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/appsettings.json
  41. 3
      nupkg/common.ps1

1
.github/workflows/build-and-test.yml

@ -59,7 +59,6 @@ jobs:
- uses: actions/setup-dotnet@master
with:
dotnet-version: 10.0.x
- name: Build All
run: ./build-all.ps1
working-directory: ./build

4
Directory.Packages.props

@ -182,6 +182,10 @@
<PackageVersion Include="System.Threading.Tasks.Extensions" Version="4.6.3" />
<PackageVersion Include="TencentCloudSDK.Sms" Version="3.0.1273" />
<PackageVersion Include="TimeZoneConverter" Version="7.0.0" />
<PackageVersion Include="TickerQ" Version="2.5.3" />
<PackageVersion Include="TickerQ.Dashboard" Version="2.5.3" />
<PackageVersion Include="TickerQ.Utilities" Version="2.5.3" />
<PackageVersion Include="TickerQ.EntityFrameworkCore" Version="2.5.3" />
<PackageVersion Include="Unidecode.NET" Version="2.1.0" />
<PackageVersion Include="xunit" Version="2.9.3" />
<PackageVersion Include="xunit.extensibility.execution" Version="2.9.3" />

8
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"
}
]
},

1
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)

125
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<AbpBackgroundJobsTickerQOptions>(options =>
{
options.AddJobConfiguration<MyBackgroundJob>(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<MyBackgroundJob2>(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<TimeTicker>` or `ICronTickerManager<CronTicker>` 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<string> 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>();
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<string>(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type);
var genericContext = new TickerFunctionContext<string>(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<TimeTicker>`:
```csharp
var timeTickerManager = context.ServiceProvider.GetRequiredService<ITimeTickerManager<TimeTicker>>();
await timeTickerManager.AddAsync(new TimeTicker
{
Function = nameof(CleanupJobs),
ExecutionTime = DateTime.UtcNow.AddSeconds(5),
Request = TickerHelper.CreateTickerRequest<string>("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.

5
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

119
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<AbpBackgroundWorkersTickerQOptions>(options =>
{
options.AddConfiguration<MyBackgroundWorker>(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<TimeTicker>` or `ICronTickerManager<CronTicker>` 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<string> 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>();
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<string>(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type);
var genericContext = new TickerFunctionContext<string>(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<CronTicker>`:
```csharp
var cronTickerManager = context.ServiceProvider.GetRequiredService<ICronTickerManager<CronTicker>>();
await cronTickerManager.AddAsync(new CronTicker
{
Function = nameof(CleanupJobs),
Expression = "0 */6 * * *", // Every 6 hours
Request = TickerHelper.CreateTickerRequest<string>("cleanup_example_file.txt"),
Retries = 2,
RetryIntervals = new[] { 60, 300 }
});
```
You can specify a cron expression instead of use `ICronTickerManager<CronTicker>` 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<string>(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type);
var genericContext = new TickerFunctionContext<string>(tickerFunctionContext, request);
await service.CleanupLogsAsync(genericContext, cancellationToken);
})));
```

5
framework/Volo.Abp.slnx

@ -166,6 +166,9 @@
<Project Path="src/Volo.Abp/Volo.Abp.csproj" />
<Project Path="src/Volo.Abp.AI.Abstractions/Volo.Abp.AI.Abstractions.csproj" />
<Project Path="src/Volo.Abp.AI/Volo.Abp.AI.csproj" />
<Project Path="src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj" />
<Project Path="src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj" />
<Project Path="src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj" />
</Folder>
<Folder Name="/test/">
<Project Path="test/AbpTestBase/AbpTestBase.csproj" />
@ -252,4 +255,4 @@
<Project Path="test/Volo.Abp.Validation.Tests/Volo.Abp.Validation.Tests.csproj" />
<Project Path="test/Volo.Abp.VirtualFileSystem.Tests/Volo.Abp.VirtualFileSystem.Tests.csproj" />
</Folder>
</Solution>
</Solution>

3
framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

24
framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net8.0;net9.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<AssemblyName>Volo.Abp.BackgroundJobs.TickerQ</AssemblyName>
<PackageId>Volo.Abp.BackgroundJobs.TickerQ</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.BackgroundJobs.Abstractions\Volo.Abp.BackgroundJobs.Abstractions.csproj" />
<ProjectReference Include="..\Volo.Abp.TickerQ\Volo.Abp.TickerQ.csproj" />
</ItemGroup>
</Project>

75
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<IOptions<AbpBackgroundJobOptions>>();
var abpBackgroundJobsTickerQOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundJobsTickerQOptions>>();
var tickerFunctionDelegates = new Dictionary<string, (string, TickerTaskPriority, TickerFunctionDelegate)>();
var requestTypes = new Dictionary<string, (string, Type)>();
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<AbpTickerQFunctionProvider>();
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<TArgs>(Type argsType)
{
return async (cancellationToken, serviceProvider, context) =>
{
var options = serviceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().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<IBackgroundJobExecuter>();
var args = await TickerRequestProvider.GetRequestAsync<TArgs>(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);
}
};
}
}

34
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<Type, AbpBackgroundJobsTimeTickerConfiguration> _configurations;
public AbpBackgroundJobsTickerQOptions()
{
_configurations = new Dictionary<Type, AbpBackgroundJobsTimeTickerConfiguration>();
}
public void AddConfiguration<TJob>(AbpBackgroundJobsTimeTickerConfiguration configuration)
{
AddConfiguration(typeof(TJob), configuration);
}
public void AddConfiguration(Type jobType, AbpBackgroundJobsTimeTickerConfiguration configuration)
{
_configurations[jobType] = configuration;
}
public AbpBackgroundJobsTimeTickerConfiguration? GetConfigurationOrNull<TJob>()
{
return GetConfigurationOrNull(typeof(TJob));
}
public AbpBackgroundJobsTimeTickerConfiguration? GetConfigurationOrNull(Type jobType)
{
return _configurations.GetValueOrDefault(jobType);
}
}

17
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; }
}

51
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<TimeTicker> TimeTickerManager { get; }
protected AbpBackgroundJobOptions Options { get; }
protected AbpBackgroundJobsTickerQOptions TickerQOptions { get; }
public AbpTickerQBackgroundJobManager(
ITimeTickerManager<TimeTicker> timeTickerManager,
IOptions<AbpBackgroundJobOptions> options,
IOptions<AbpBackgroundJobsTickerQOptions> tickerQOptions)
{
TimeTickerManager = timeTickerManager;
Options = options.Value;
TickerQOptions = tickerQOptions.Value;
}
public virtual async Task<string> EnqueueAsync<TArgs>(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<TArgs>(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();
}
}

3
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

24
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net8.0;net9.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<AssemblyName>Volo.Abp.BackgroundWorkers.TickerQ</AssemblyName>
<PackageId>Volo.Abp.BackgroundWorkers.TickerQ</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.BackgroundWorkers\Volo.Abp.BackgroundWorkers.csproj" />
<ProjectReference Include="..\Volo.Abp.TickerQ\Volo.Abp.TickerQ.csproj" />
</ItemGroup>
</Project>

12
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; }
}

37
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<AbpTickerQBackgroundWorkersProvider>();
var cronTickerManager = context.ServiceProvider.GetRequiredService<ICronTickerManager<CronTicker>>();
var abpBackgroundWorkersTickerQOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkersTickerQOptions>>().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);
}
}
}

34
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<Type, AbpBackgroundWorkersCronTickerConfiguration> _onfigurations;
public AbpBackgroundWorkersTickerQOptions()
{
_onfigurations = new Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration>();
}
public void AddConfiguration<TWorker>(AbpBackgroundWorkersCronTickerConfiguration configuration)
{
AddConfiguration(typeof(TWorker), configuration);
}
public void AddConfiguration(Type workerType, AbpBackgroundWorkersCronTickerConfiguration configuration)
{
_onfigurations[workerType] = configuration;
}
public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull<TJob>()
{
return GetConfigurationOrNull(typeof(TJob));
}
public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull(Type workerType)
{
return _onfigurations.GetValueOrDefault(workerType);
}
}

105
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<AbpBackgroundWorkersTickerQOptions> 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.");
}
}

14
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<string, AbpTickerQCronBackgroundWorker> BackgroundWorkers { get;}
public AbpTickerQBackgroundWorkersProvider()
{
BackgroundWorkers = new Dictionary<string, AbpTickerQCronBackgroundWorker>();
}
}

12
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!;
}

73
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<AsyncPeriodicBackgroundWorkerBase, PeriodicBackgroundWorkerContext, Task>? _doWorkAsyncDelegate;
private readonly Action<PeriodicBackgroundWorkerBase, PeriodicBackgroundWorkerContext>? _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<Func<AsyncPeriodicBackgroundWorkerBase, PeriodicBackgroundWorkerContext, Task>>(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<Action<PeriodicBackgroundWorkerBase, PeriodicBackgroundWorkerContext>>(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;
}
}
}

3
framework/src/Volo.Abp.TickerQ/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.TickerQ/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

20
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<AbpTickerQFunctionProvider>();
TickerFunctionProvider.RegisterFunctions(abpTickerQFunctionProvider.Functions);
TickerFunctionProvider.RegisterRequestType(abpTickerQFunctionProvider.RequestTypes);
app.UseTickerQ(qStartMode);
return app;
}
}

27
framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj

@ -0,0 +1,27 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.1;net8.0;net9.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<AssemblyName>Volo.Abp.TickerQ</AssemblyName>
<PackageId>Volo.Abp.TickerQ</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<PackageReference Include="TickerQ" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
</ItemGroup>
</Project>

20
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<string, (string, TickerTaskPriority, TickerFunctionDelegate)> Functions { get;}
public Dictionary<string, (string, Type)> RequestTypes { get; }
public AbpTickerQFunctionProvider()
{
Functions = new Dictionary<string, (string, TickerTaskPriority, TickerFunctionDelegate)>();
RequestTypes = new Dictionary<string, (string, Type)>();
}
}

16
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());
});
}
}

3
modules/background-jobs/Volo.Abp.BackgroundJobs.slnx

@ -5,6 +5,7 @@
<Project Path="app/Volo.Abp.BackgroundJobs.DemoApp.RabbitMq/Volo.Abp.BackgroundJobs.DemoApp.RabbitMq.csproj" />
<Project Path="app/Volo.Abp.BackgroundJobs.DemoApp.Shared/Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj" />
<Project Path="app/Volo.Abp.BackgroundJobs.DemoApp/Volo.Abp.BackgroundJobs.DemoApp.csproj" />
<Project Path="app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Volo.Abp.BackgroundJobs.DemoApp.TickerQ.csproj" />
</Folder>
<Folder Name="/src/">
<Project Path="src/Volo.Abp.BackgroundJobs.Domain.Shared/Volo.Abp.BackgroundJobs.Domain.Shared.csproj" />
@ -19,4 +20,4 @@
<Project Path="test/Volo.Abp.BackgroundJobs.MongoDB.Tests/Volo.Abp.BackgroundJobs.MongoDB.Tests.csproj" />
<Project Path="test/Volo.Abp.BackgroundJobs.TestBase/Volo.Abp.BackgroundJobs.TestBase.csproj" />
</Folder>
</Solution>
</Solution>

3
modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.Shared/DemoAppSharedModule.cs

@ -4,9 +4,6 @@ using Volo.Abp.Modularity;
namespace Volo.Abp.BackgroundJobs.DemoApp.Shared
{
[DependsOn(
typeof(AbpBackgroundJobsModule)
)]
public class DemoAppSharedModule : AbpModule
{
public override void OnPostApplicationInitialization(ApplicationInitializationContext context)

15
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<string> tickerContext, CancellationToken cancellationToken)
{
var logFileName = tickerContext.Request;
Console.WriteLine($"Cleaning up log file: {logFileName} at {DateTime.Now}");
}
}

140
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<AbpBackgroundJobsTickerQOptions>(options =>
{
options.AddConfiguration<WriteToConsoleGreenJob>(new AbpBackgroundJobsTimeTickerConfiguration()
{
Retries = 3,
RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min,
Priority = TickerTaskPriority.High
});
options.AddConfiguration<WriteToConsoleYellowJob>(new AbpBackgroundJobsTimeTickerConfiguration()
{
Retries = 5,
RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min
});
});
Configure<AbpBackgroundWorkersTickerQOptions>(options =>
{
options.AddConfiguration<MyBackgroundWorker>(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>();
abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) =>
{
var service = new CleanupJobs();
var request = await TickerRequestProvider.GetRequestAsync<string>(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type);
var genericContext = new TickerFunctionContext<string>(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<IBackgroundWorkerManager>();
await backgroundWorkerManager.AddAsync(context.ServiceProvider.GetRequiredService<MyBackgroundWorker>());
var app = context.GetApplicationBuilder();
app.UseAbpTickerQ();
var timeTickerManager = context.ServiceProvider.GetRequiredService<ITimeTickerManager<TimeTicker>>();
await timeTickerManager.AddAsync(new TimeTicker
{
Function = nameof(CleanupJobs),
ExecutionTime = DateTime.UtcNow.AddSeconds(5),
Request = TickerHelper.CreateTickerRequest<string>("cleanup_example_file.txt"),
Retries = 3,
RetryIntervals = new[] { 30, 60, 120 }, // Retry after 30s, 60s, then 2min
});
var cronTickerManager = context.ServiceProvider.GetRequiredService<ICronTickerManager<CronTicker>>();
await cronTickerManager.AddAsync(new CronTicker
{
Function = nameof(CleanupJobs),
Expression = "* * * * *", // Every minute
Request = TickerHelper.CreateTickerRequest<string>("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<IBackgroundJobManager>();
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<ITimeTickerManager<TimeTicker>>();
var result = await timeTickerManager.DeleteAsync(Guid.Parse(jobId));
}
}

22
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}");
}
}

19
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<DemoAppTickerQModule>();
var app = builder.Build();
await app.InitializeApplicationAsync();
await app.RunAsync();
}
}

13
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"
}
}
}
}

26
modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/Volo.Abp.BackgroundJobs.DemoApp.TickerQ.csproj

@ -0,0 +1,26 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.BackgroundJobs.TickerQ\Volo.Abp.BackgroundJobs.TickerQ.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.BackgroundWorkers.TickerQ\Volo.Abp.BackgroundWorkers.TickerQ.csproj" />
<ProjectReference Include="..\Volo.Abp.BackgroundJobs.DemoApp.Shared\Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore\Volo.Abp.AspNetCore.csproj" />
<PackageReference Include="TickerQ.Dashboard" />
</ItemGroup>
<ItemGroup>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
<None Update="appsettings.json">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>

8
modules/background-jobs/app/Volo.Abp.BackgroundJobs.DemoApp.TickerQ/appsettings.json

@ -0,0 +1,8 @@
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
}
}

3
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",

Loading…
Cancel
Save