Browse Source

feat: Introduce TickerQ background job integration

pull/23802/head
maliming 2 months ago
parent
commit
41770611c2
No known key found for this signature in database GPG Key ID: A646B9CB645ECEA4
  1. 4
      Directory.Packages.props
  2. 7
      framework/Volo.Abp.slnx
  3. 3
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xml
  4. 30
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/FodyWeavers.xsd
  5. 24
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo.Abp.BackgroundJobs.TickerQ.csproj
  6. 62
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs
  7. 39
      framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/TickerQBackgroundJobManager.cs
  8. 112
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/README.md
  9. 39
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Samples/SampleTickerQModule.cs
  10. 119
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Samples/SampleTickerQWorkers.cs
  11. 3
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.Quartz.abppkg
  12. 68
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.Quartz.abppkg.analyze.json
  13. 7
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.TickerQ.csproj
  14. 29
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersTickerQServiceCollectionExtensions.cs
  15. 31
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkerTickerQOptions.cs
  16. 42
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs
  17. 44
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/ITickerQBackgroundWorker.cs
  18. 46
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQBackgroundWorkerBase.cs
  19. 131
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQBackgroundWorkerManager.cs
  20. 128
      framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQPeriodicBackgroundWorkerAdapter.cs
  21. 3
      framework/src/Volo.Abp.TickerQ/FodyWeavers.xml
  22. 30
      framework/src/Volo.Abp.TickerQ/FodyWeavers.xsd
  23. 28
      framework/src/Volo.Abp.TickerQ/Volo.Abp.TickerQ.csproj
  24. 17
      framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs
  25. 3
      nupkg/common.ps1

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" />

7
framework/Volo.Abp.slnx

@ -164,8 +164,9 @@
<Project Path="src/Volo.Abp.Validation/Volo.Abp.Validation.csproj" />
<Project Path="src/Volo.Abp.VirtualFileSystem/Volo.Abp.VirtualFileSystem.csproj" />
<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 +253,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>

62
framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/AbpBackgroundJobsTickerQModule.cs

@ -0,0 +1,62 @@
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 tickerFunctionDelegateDict = 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])!;
tickerFunctionDelegateDict.TryAdd(jobConfiguration.JobName, (string.Empty, TickerTaskPriority.Normal, tickerFunctionDelegate));
requestTypes.TryAdd(jobConfiguration.JobName, (jobConfiguration.ArgsType.FullName, jobConfiguration.ArgsType)!);
}
TickerFunctionProvider.RegisterFunctions(tickerFunctionDelegateDict);
TickerFunctionProvider.RegisterRequestType(requestTypes);
}
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);
}
};
}
}

39
framework/src/Volo.Abp.BackgroundJobs.TickerQ/Volo/Abp/BackgroundJobs/TickerQ/TickerQBackgroundJobManager.cs

@ -0,0 +1,39 @@
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 TickerQBackgroundJobManager : IBackgroundJobManager, ITransientDependency
{
protected ITimeTickerManager<TimeTicker> TimeTickerManager { get; }
protected AbpBackgroundJobOptions Options { get; }
public TickerQBackgroundJobManager(ITimeTickerManager<TimeTicker> timeTickerManager, IOptions<AbpBackgroundJobOptions> options)
{
TimeTickerManager = timeTickerManager;
Options = options.Value;
}
public virtual async Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null)
{
var result = await TimeTickerManager.AddAsync(new TimeTicker
{
Id = Guid.NewGuid(),
Function = Options.GetJob(typeof(TArgs)).JobName,
ExecutionTime = delay == null ? DateTime.UtcNow : DateTime.UtcNow.Add(delay.Value),
Request = TickerHelper.CreateTickerRequest<TArgs>(args),
//TODO: Make these configurable
Retries = 3,
RetryIntervals = [30, 60, 120], // Retry after 30s, 60s, then 2min
});
return result.Result.Id.ToString();
}
}

112
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/README.md

@ -1,112 +0,0 @@
# ABP TickerQ Background Workers Integration
This package provides integration between [TickerQ](https://github.com/dotnetdevelopersdz/TickerQ) and the ABP Framework's background worker system.
## About TickerQ
TickerQ 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.
Key features:
- **Performance**: Reflection-free design with source generators
- **EF Core Integration**: Native Entity Framework Core support
- **Flexible Scheduling**: Cron expressions and time-based execution
- **Real-time Dashboard**: Built-in monitoring and management
- **Modern .NET**: Built for modern .NET with async/await
## Installation
Install the NuGet package:
```bash
dotnet add package Volo.Abp.BackgroundWorkers.TickerQ
```
Or using the ABP CLI:
```bash
abp add-package Volo.Abp.BackgroundWorkers.TickerQ
```
## Usage
1. Add the module dependency to your ABP module:
```csharp
[DependsOn(typeof(AbpBackgroundWorkersTickerQModule))]
public class YourModule : AbpModule
{
// ...
}
```
2. Create your background worker:
```csharp
public class MyTickerQWorker : TickerQBackgroundWorkerBase
{
public MyTickerQWorker()
{
JobId = nameof(MyTickerQWorker);
CronExpression = "0 */5 * ? * *"; // Every 5 minutes
Priority = 1;
MaxRetryAttempts = 3;
}
public override Task DoWorkAsync(CancellationToken cancellationToken = default)
{
Logger.LogInformation("TickerQ worker executed!");
// Your work logic here
return Task.CompletedTask;
}
}
```
3. Configure options (optional):
```csharp
Configure<AbpBackgroundWorkerTickerQOptions>(options =>
{
options.IsAutoRegisterEnabled = true;
options.DefaultCronExpression = "0 * * ? * *";
options.DefaultMaxRetryAttempts = 3;
options.DefaultPriority = 0;
});
```
## Migration from Other Background Workers
The integration provides adapters for existing background workers:
- `AsyncPeriodicBackgroundWorkerBase` workers will work automatically
- `PeriodicBackgroundWorkerBase` workers will work automatically
- Timer periods are converted to appropriate cron expressions
## Features
- **Automatic Registration**: Workers are auto-registered by default
- **Dependency Injection**: Full DI support in workers
- **Error Handling**: Built-in retry logic and error handling
- **Performance**: Benefits from TickerQ's reflection-free design
- **Compatibility**: Works with existing ABP background workers
## Samples
See the `Samples` folder for example implementations demonstrating:
- Basic worker usage
- Error handling and retries
- Dependency injection
- Configuration options
## Documentation
For detailed documentation, see: [docs/en/framework/infrastructure/background-workers/tickerq.md](../../../docs/en/framework/infrastructure/background-workers/tickerq.md)
## Requirements
- .NET 8.0 or later
- ABP Framework 9.0 or later
- TickerQ package (when available)
## Status
This integration is ready for use once the TickerQ package becomes available on NuGet. The implementation follows ABP's established patterns for background worker integrations (Quartz, Hangfire) and provides a seamless migration path.

39
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Samples/SampleTickerQModule.cs

@ -1,39 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundWorkers.TickerQ.Samples;
using Volo.Abp.Modularity;
namespace Volo.Abp.BackgroundWorkers.TickerQ;
/// <summary>
/// Sample module demonstrating how to use TickerQ background workers in an ABP application.
/// </summary>
[DependsOn(typeof(AbpBackgroundWorkersTickerQModule))]
public class SampleTickerQModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Configure TickerQ options
Configure<AbpBackgroundWorkerTickerQOptions>(options =>
{
options.IsAutoRegisterEnabled = true;
options.DefaultCronExpression = "0 * * ? * *"; // Every minute
options.DefaultMaxRetryAttempts = 3;
options.DefaultPriority = 0;
});
// Register sample workers as transient services so they can be injected with dependencies
context.Services.AddTransient<SampleTickerQWorker>();
context.Services.AddTransient<SampleErrorHandlingTickerQWorker>();
context.Services.AddTransient<SampleDependencyInjectionTickerQWorker>();
}
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context)
{
// Sample workers with AutoRegister = true will be registered automatically
// But you can also register them manually if needed:
// await context.AddBackgroundWorkerAsync<SampleTickerQWorker>();
// await context.AddBackgroundWorkerAsync<SampleErrorHandlingTickerQWorker>();
// await context.AddBackgroundWorkerAsync<SampleDependencyInjectionTickerQWorker>();
}
}

119
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Samples/SampleTickerQWorkers.cs

@ -1,119 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
namespace Volo.Abp.BackgroundWorkers.TickerQ.Samples;
/// <summary>
/// Sample TickerQ background worker that demonstrates basic usage.
/// This worker runs every 5 minutes and performs a simple logging operation.
/// </summary>
public class SampleTickerQWorker : TickerQBackgroundWorkerBase
{
public SampleTickerQWorker()
{
// Configure the worker
JobId = nameof(SampleTickerQWorker);
CronExpression = "0 */5 * ? * *"; // Every 5 minutes
Priority = 1; // Higher than default priority
MaxRetryAttempts = 5; // Retry up to 5 times on failure
}
public override Task DoWorkAsync(CancellationToken cancellationToken = default)
{
Logger.LogInformation("Sample TickerQ worker executed at {Time}", DateTime.Now);
// Simulate some work
Logger.LogDebug("Processing sample work...");
// Your background task logic goes here
// For example:
// - Process pending orders
// - Send scheduled notifications
// - Cleanup old data
// - Generate reports
Logger.LogInformation("Sample TickerQ worker completed successfully");
return Task.CompletedTask;
}
}
/// <summary>
/// Sample TickerQ background worker that demonstrates error handling and retry logic.
/// </summary>
public class SampleErrorHandlingTickerQWorker : TickerQBackgroundWorkerBase
{
private static int _executionCount = 0;
public SampleErrorHandlingTickerQWorker()
{
JobId = nameof(SampleErrorHandlingTickerQWorker);
CronExpression = "0 */10 * ? * *"; // Every 10 minutes
MaxRetryAttempts = 3;
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = default)
{
var currentCount = Interlocked.Increment(ref _executionCount);
Logger.LogInformation("Error handling worker executed {Count} times", currentCount);
// Simulate intermittent failures for demonstration
if (currentCount % 3 == 0)
{
Logger.LogWarning("Simulating a temporary failure (will retry)");
throw new InvalidOperationException("Simulated failure for demonstration");
}
// Simulate successful work
await Task.Delay(100, cancellationToken); // Simulate some async work
Logger.LogInformation("Error handling worker completed successfully");
}
}
/// <summary>
/// Sample TickerQ background worker that demonstrates working with dependency injection.
/// </summary>
public class SampleDependencyInjectionTickerQWorker : TickerQBackgroundWorkerBase
{
// In a real application, you would inject your repositories, services, etc.
// private readonly IMyRepository _myRepository;
// private readonly IMyService _myService;
public SampleDependencyInjectionTickerQWorker(
// IMyRepository myRepository,
// IMyService myService
)
{
// _myRepository = myRepository;
// _myService = myService;
JobId = nameof(SampleDependencyInjectionTickerQWorker);
CronExpression = "0 0 */6 ? * *"; // Every 6 hours
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = default)
{
Logger.LogInformation("Dependency injection worker started");
try
{
// Example of using injected services
// var entities = await _myRepository.GetListAsync(cancellationToken: cancellationToken);
// await _myService.ProcessEntitiesAsync(entities, cancellationToken);
// For demonstration, just simulate the work
await Task.Delay(50, cancellationToken);
Logger.LogInformation("Dependency injection worker processed data successfully");
}
catch (Exception ex)
{
Logger.LogError(ex, "Error occurred in dependency injection worker");
throw; // Re-throw to trigger retry logic
}
}
}

3
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.Quartz.abppkg

@ -1,3 +0,0 @@
{
"role": "lib.framework"
}

68
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo.Abp.BackgroundWorkers.Quartz.abppkg.analyze.json

@ -1,68 +0,0 @@
{
"name": "Volo.Abp.BackgroundWorkers.Quartz",
"hash": "",
"contents": [
{
"namespace": "Volo.Abp.BackgroundWorkers.Quartz",
"dependsOnModules": [
{
"declaringAssemblyName": "Volo.Abp.BackgroundWorkers",
"namespace": "Volo.Abp.BackgroundWorkers",
"name": "AbpBackgroundWorkersModule"
},
{
"declaringAssemblyName": "Volo.Abp.Quartz",
"namespace": "Volo.Abp.Quartz",
"name": "AbpQuartzModule"
}
],
"implementingInterfaces": [
{
"name": "IAbpModule",
"namespace": "Volo.Abp.Modularity",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.Modularity.IAbpModule"
},
{
"name": "IOnPreApplicationInitialization",
"namespace": "Volo.Abp.Modularity",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.Modularity.IOnPreApplicationInitialization"
},
{
"name": "IOnApplicationInitialization",
"namespace": "Volo.Abp",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.IOnApplicationInitialization"
},
{
"name": "IOnPostApplicationInitialization",
"namespace": "Volo.Abp.Modularity",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.Modularity.IOnPostApplicationInitialization"
},
{
"name": "IOnApplicationShutdown",
"namespace": "Volo.Abp",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.IOnApplicationShutdown"
},
{
"name": "IPreConfigureServices",
"namespace": "Volo.Abp.Modularity",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.Modularity.IPreConfigureServices"
},
{
"name": "IPostConfigureServices",
"namespace": "Volo.Abp.Modularity",
"declaringAssemblyName": "Volo.Abp.Core",
"fullName": "Volo.Abp.Modularity.IPostConfigureServices"
}
],
"contentType": "abpModule",
"name": "AbpBackgroundWorkersQuartzModule",
"summary": null
}
]
}

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

@ -4,7 +4,7 @@
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.0;net10.0</TargetFrameworks>
<TargetFrameworks>netstandard2.1;net8.0;net9.0;net10.0</TargetFrameworks>
<Nullable>enable</Nullable>
<WarningsAsErrors>Nullable</WarningsAsErrors>
<AssemblyName>Volo.Abp.BackgroundWorkers.TickerQ</AssemblyName>
@ -18,8 +18,7 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.BackgroundWorkers\Volo.Abp.BackgroundWorkers.csproj" />
<!-- Note: This would need to reference the actual TickerQ package when it becomes available -->
<!-- <PackageReference Include="TickerQ" Version="*" /> -->
<ProjectReference Include="..\Volo.Abp.TickerQ\Volo.Abp.TickerQ.csproj" />
</ItemGroup>
</Project>
</Project>

29
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/AbpBackgroundWorkersTickerQServiceCollectionExtensions.cs

@ -1,29 +0,0 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.BackgroundWorkers.TickerQ;
namespace Volo.Abp.BackgroundWorkers;
/// <summary>
/// Extension methods for TickerQ background worker integration.
/// </summary>
public static class AbpBackgroundWorkersTickerQServiceCollectionExtensions
{
/// <summary>
/// Adds TickerQ background worker services to the service collection.
/// This method provides additional configuration options beyond the basic module registration.
/// </summary>
/// <param name="services">The service collection.</param>
/// <param name="configure">Optional configuration action.</param>
/// <returns>The service collection for chaining.</returns>
public static IServiceCollection AddAbpTickerQBackgroundWorkers(
this IServiceCollection services,
Action<AbpBackgroundWorkerTickerQOptions>? configure = null)
{
if (configure != null)
{
services.Configure(configure);
}
return services;
}
}

31
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkerTickerQOptions.cs

@ -1,31 +0,0 @@
namespace Volo.Abp.BackgroundWorkers.TickerQ;
/// <summary>
/// Options for TickerQ background workers.
/// </summary>
public class AbpBackgroundWorkerTickerQOptions
{
/// <summary>
/// Gets or sets whether automatic registration is enabled for TickerQ workers.
/// Default is true.
/// </summary>
public bool IsAutoRegisterEnabled { get; set; } = true;
/// <summary>
/// Gets or sets the default cron expression for workers that don't specify one.
/// Default is every minute: "0 * * ? * *"
/// </summary>
public string DefaultCronExpression { get; set; } = "0 * * ? * *";
/// <summary>
/// Gets or sets the default maximum retry attempts.
/// Default is 3.
/// </summary>
public int DefaultMaxRetryAttempts { get; set; } = 3;
/// <summary>
/// Gets or sets the default priority for workers.
/// Default is 0 (normal priority).
/// </summary>
public int DefaultPriority { get; set; } = 0;
}

42
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpBackgroundWorkersTickerQModule.cs

@ -1,42 +1,10 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Modularity;
using Volo.Abp.Modularity;
using Volo.Abp.TickerQ;
namespace Volo.Abp.BackgroundWorkers.TickerQ;
/// <summary>
/// ABP module for TickerQ background workers integration.
/// TickerQ 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.
/// </summary>
[DependsOn(
typeof(AbpBackgroundWorkersModule))]
[DependsOn(typeof(AbpBackgroundWorkersModule), typeof(AbpTickerQModule))]
public class AbpBackgroundWorkersTickerQModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// Register TickerQ-specific services and configure options
context.Services.Configure<AbpBackgroundWorkerTickerQOptions>(options =>
{
// Set up default options - users can override these in their modules
});
// The TickerQBackgroundWorkerManager will automatically replace the default manager
// due to the [Dependency(ReplaceServices = true)] attribute
}
public override void OnPreApplicationInitialization(ApplicationInitializationContext context)
{
// Check if background workers are enabled
var options = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkerOptions>>().Value;
if (!options.IsEnabled)
{
// If background workers are disabled, we don't need to initialize TickerQ
return;
}
// Initialize TickerQ background worker manager
var tickerQManager = context.ServiceProvider.GetRequiredService<TickerQBackgroundWorkerManager>();
tickerQManager.Initialize();
}
}
}

44
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/ITickerQBackgroundWorker.cs

@ -1,44 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.BackgroundWorkers.TickerQ;
/// <summary>
/// Interface for TickerQ background workers.
/// TickerQ is a fast, reflection-free background task scheduler for .NET.
/// </summary>
public interface ITickerQBackgroundWorker : IBackgroundWorker
{
/// <summary>
/// Gets or sets the cron expression for the job scheduling.
/// </summary>
string? CronExpression { get; set; }
/// <summary>
/// Gets or sets the job identifier.
/// </summary>
string? JobId { get; set; }
/// <summary>
/// Gets or sets whether to automatically register this worker.
/// Default is true.
/// </summary>
bool AutoRegister { get; set; }
/// <summary>
/// Gets or sets the job priority.
/// </summary>
int Priority { get; set; }
/// <summary>
/// Gets or sets the maximum retry attempts for failed jobs.
/// </summary>
int MaxRetryAttempts { get; set; }
/// <summary>
/// The main work execution method.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the work execution.</returns>
Task DoWorkAsync(CancellationToken cancellationToken = default);
}

46
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQBackgroundWorkerBase.cs

@ -1,46 +0,0 @@
using System.Threading;
using System.Threading.Tasks;
namespace Volo.Abp.BackgroundWorkers.TickerQ;
/// <summary>
/// Base class for TickerQ background workers.
/// TickerQ is a fast, reflection-free background task scheduler for .NET.
/// </summary>
public abstract class TickerQBackgroundWorkerBase : BackgroundWorkerBase, ITickerQBackgroundWorker
{
/// <summary>
/// Gets or sets the cron expression for the job scheduling.
/// </summary>
public string? CronExpression { get; set; }
/// <summary>
/// Gets or sets the job identifier.
/// </summary>
public string? JobId { get; set; }
/// <summary>
/// Gets or sets whether to automatically register this worker.
/// Default is true.
/// </summary>
public bool AutoRegister { get; set; } = true;
/// <summary>
/// Gets or sets the job priority.
/// Default is 0 (normal priority).
/// </summary>
public int Priority { get; set; } = 0;
/// <summary>
/// Gets or sets the maximum retry attempts for failed jobs.
/// Default is 3.
/// </summary>
public int MaxRetryAttempts { get; set; } = 3;
/// <summary>
/// The main work execution method that must be implemented by derived classes.
/// </summary>
/// <param name="cancellationToken">The cancellation token.</param>
/// <returns>A task representing the work execution.</returns>
public abstract Task DoWorkAsync(CancellationToken cancellationToken = default);
}

131
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQBackgroundWorkerManager.cs

@ -1,131 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.DynamicProxy;
namespace Volo.Abp.BackgroundWorkers.TickerQ;
/// <summary>
/// TickerQ implementation of the background worker manager.
/// Replaces the default background worker manager when TickerQ integration is enabled.
/// </summary>
[Dependency(ReplaceServices = true)]
public class TickerQBackgroundWorkerManager : BackgroundWorkerManager, ISingletonDependency
{
private readonly AbpBackgroundWorkerTickerQOptions _options;
private readonly IServiceProvider _serviceProvider;
private bool _isInitialized;
public TickerQBackgroundWorkerManager(
IOptions<AbpBackgroundWorkerTickerQOptions> options,
IServiceProvider serviceProvider)
{
_options = options.Value;
_serviceProvider = serviceProvider;
}
public override async Task StartAsync(CancellationToken cancellationToken = default)
{
Logger.LogInformation("Starting TickerQ Background Worker Manager...");
if (!_isInitialized)
{
await InitializeAsync();
}
await base.StartAsync(cancellationToken);
Logger.LogInformation("TickerQ Background Worker Manager started.");
}
public override async Task StopAsync(CancellationToken cancellationToken = default)
{
Logger.LogInformation("Stopping TickerQ Background Worker Manager...");
await base.StopAsync(cancellationToken);
Logger.LogInformation("TickerQ Background Worker Manager stopped.");
}
public override async Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default)
{
if (worker is ITickerQBackgroundWorker tickerQWorker)
{
await ScheduleTickerQJobAsync(tickerQWorker, cancellationToken);
}
else
{
// For non-TickerQ workers, use the default behavior
await base.AddAsync(worker, cancellationToken);
}
}
protected virtual async Task InitializeAsync()
{
Logger.LogDebug("Initializing TickerQ Background Worker Manager...");
// TODO: Initialize TickerQ scheduler here when the actual TickerQ library is available
// This would involve setting up the TickerQ configuration, database connections, etc.
_isInitialized = true;
Logger.LogDebug("TickerQ Background Worker Manager initialized.");
await Task.CompletedTask;
}
protected virtual async Task ScheduleTickerQJobAsync(ITickerQBackgroundWorker worker, CancellationToken cancellationToken = default)
{
Logger.LogInformation("Scheduling TickerQ job: {JobId}", worker.JobId ?? worker.GetType().Name);
try
{
// TODO: Implement actual TickerQ job scheduling when the library is available
// This would involve:
// 1. Creating a TickerQ job definition
// 2. Setting up the cron expression or time-based trigger
// 3. Configuring retry policy and priority
// 4. Registering the job with TickerQ scheduler
// For now, we'll just log the configuration
Logger.LogDebug("TickerQ job configuration: JobId={JobId}, CronExpression={CronExpression}, Priority={Priority}, MaxRetryAttempts={MaxRetryAttempts}",
worker.JobId ?? worker.GetType().Name,
worker.CronExpression ?? _options.DefaultCronExpression,
worker.Priority,
worker.MaxRetryAttempts);
await Task.CompletedTask;
}
catch (Exception ex)
{
Logger.LogError(ex, "Failed to schedule TickerQ job: {JobId}", worker.JobId ?? worker.GetType().Name);
throw;
}
}
public void Initialize()
{
// Automatically register TickerQ background workers if auto-registration is enabled
if (!_options.IsAutoRegisterEnabled)
{
return;
}
Logger.LogDebug("Auto-registering TickerQ background workers...");
var backgroundWorkers = _serviceProvider.GetServices<IBackgroundWorker>();
foreach (var backgroundWorker in backgroundWorkers)
{
if (backgroundWorker is ITickerQBackgroundWorker tickerQWorker && tickerQWorker.AutoRegister)
{
AddAsync(tickerQWorker).ConfigureAwait(false).GetAwaiter().GetResult();
}
}
Logger.LogDebug("Auto-registration of TickerQ background workers completed.");
}
}

128
framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/TickerQPeriodicBackgroundWorkerAdapter.cs

@ -1,128 +0,0 @@
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.BackgroundWorkers.TickerQ;
/// <summary>
/// Adapter to enable existing periodic background workers to work with TickerQ.
/// This allows users to migrate from the default background worker implementation to TickerQ
/// without changing their existing worker code.
/// </summary>
public class TickerQPeriodicBackgroundWorkerAdapter<TWorker> : TickerQBackgroundWorkerBase, ITransientDependency
where TWorker : class, IBackgroundWorker
{
private readonly IServiceProvider _serviceProvider;
public TickerQPeriodicBackgroundWorkerAdapter(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
// Set default job ID based on the worker type
JobId = typeof(TWorker).Name;
}
public override async Task DoWorkAsync(CancellationToken cancellationToken = default)
{
Logger.LogDebug("Executing adapted periodic worker: {WorkerType}", typeof(TWorker).Name);
using var scope = _serviceProvider.CreateScope();
var worker = scope.ServiceProvider.GetRequiredService<TWorker>();
try
{
if (worker is IPeriodicBackgroundWorker periodicWorker)
{
await periodicWorker.DoWorkAsync(cancellationToken);
}
else if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicWorker)
{
await asyncPeriodicWorker.DoWorkAsync(cancellationToken);
}
else if (worker is PeriodicBackgroundWorkerBase syncPeriodicWorker)
{
syncPeriodicWorker.DoWork();
await Task.CompletedTask;
}
else
{
Logger.LogWarning("Worker {WorkerType} is not a supported periodic worker type", typeof(TWorker).Name);
}
}
catch (Exception ex)
{
Logger.LogError(ex, "Error executing adapted periodic worker: {WorkerType}", typeof(TWorker).Name);
throw;
}
}
/// <summary>
/// Configures the adapter based on the original worker's settings.
/// </summary>
public virtual TickerQPeriodicBackgroundWorkerAdapter<TWorker> Configure(TWorker originalWorker)
{
// Try to extract timing information from the original worker
if (originalWorker is AsyncPeriodicBackgroundWorkerBase asyncWorker)
{
// Convert timer period to cron expression (approximate)
if (asyncWorker.Timer?.Period != null)
{
var periodMinutes = asyncWorker.Timer.Period / 60000; // Convert ms to minutes
if (periodMinutes < 1)
{
CronExpression = "*/30 * * ? * *"; // Every 30 seconds for very short periods
}
else if (periodMinutes == 1)
{
CronExpression = "0 * * ? * *"; // Every minute
}
else if (periodMinutes < 60)
{
CronExpression = $"0 */{periodMinutes} * ? * *"; // Every N minutes
}
else
{
var hours = periodMinutes / 60;
CronExpression = $"0 0 */{hours} ? * *"; // Every N hours
}
}
// Use cron expression if available
if (!string.IsNullOrEmpty(asyncWorker.CronExpression))
{
CronExpression = asyncWorker.CronExpression;
}
}
else if (originalWorker is PeriodicBackgroundWorkerBase syncWorker)
{
// Similar logic for sync workers
if (syncWorker.Timer?.Period != null)
{
var periodMinutes = syncWorker.Timer.Period / 60000;
if (periodMinutes < 1)
{
CronExpression = "*/30 * * ? * *";
}
else if (periodMinutes == 1)
{
CronExpression = "0 * * ? * *";
}
else if (periodMinutes < 60)
{
CronExpression = $"0 */{periodMinutes} * ? * *";
}
else
{
var hours = periodMinutes / 60;
CronExpression = $"0 0 */{hours} ? * *";
}
}
}
return this;
}
}

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>

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

@ -0,0 +1,28 @@
<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" />
<PackageReference Include="TickerQ.Dashboard" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
</ItemGroup>
</Project>

17
framework/src/Volo.Abp.TickerQ/Volo/Abp/TickerQ/AbpTickerQModule.cs

@ -0,0 +1,17 @@
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
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