mirror of https://github.com/abpframework/abp.git
11 changed files with 689 additions and 2 deletions
@ -0,0 +1,211 @@ |
|||||
|
# TickerQ Background Worker Manager |
||||
|
|
||||
|
[TickerQ](https://github.com/dotnetdevelopersdz/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. You can integrate TickerQ with the ABP Framework to use it instead of the [default background worker manager](../background-workers). |
||||
|
|
||||
|
The major advantages of TickerQ include: |
||||
|
- **Performance**: Reflection-free design with source generators for optimal performance |
||||
|
- **EF Core Integration**: Native support for Entity Framework Core for job persistence |
||||
|
- **Flexible Scheduling**: Support for both cron expressions and time-based execution |
||||
|
- **Real-time Dashboard**: Built-in dashboard for monitoring and managing background jobs |
||||
|
- **Modern .NET**: Built for modern .NET with async/await support throughout |
||||
|
|
||||
|
## 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 |
||||
|
```` |
||||
|
|
||||
|
### Manual Installation |
||||
|
|
||||
|
If you want to manually install: |
||||
|
|
||||
|
1. Add the [Volo.Abp.BackgroundWorkers.TickerQ](https://www.nuget.org/packages/Volo.Abp.BackgroundWorkers.TickerQ) NuGet package to your project: |
||||
|
|
||||
|
```` |
||||
|
dotnet add package Volo.Abp.BackgroundWorkers.TickerQ |
||||
|
```` |
||||
|
|
||||
|
2. Add the `AbpBackgroundWorkersTickerQModule` to the dependency list of your module: |
||||
|
|
||||
|
````csharp |
||||
|
[DependsOn( |
||||
|
//...other dependencies |
||||
|
typeof(AbpBackgroundWorkersTickerQModule) //Add the new module dependency |
||||
|
)] |
||||
|
public class YourModule : AbpModule |
||||
|
{ |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
> TickerQ background worker integration provides an adapter `TickerQPeriodicBackgroundWorkerAdapter` to automatically load any `PeriodicBackgroundWorkerBase` and `AsyncPeriodicBackgroundWorkerBase` derived classes as `ITickerQBackgroundWorker` instances. This allows you to easily switch over to use TickerQ as the background manager even if you have existing background workers that are based on the [default background workers implementation](../background-workers). |
||||
|
|
||||
|
## Configuration |
||||
|
|
||||
|
You need to configure TickerQ with your preferred storage provider. TickerQ supports various storage options including Entity Framework Core. |
||||
|
|
||||
|
1. First, configure TickerQ in your module's `ConfigureServices` method: |
||||
|
|
||||
|
````csharp |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
var configuration = context.Services.GetConfiguration(); |
||||
|
var hostingEnvironment = context.Services.GetHostingEnvironment(); |
||||
|
|
||||
|
//... other configurations. |
||||
|
|
||||
|
ConfigureTickerQ(context, configuration); |
||||
|
} |
||||
|
|
||||
|
private void ConfigureTickerQ(ServiceConfigurationContext context, IConfiguration configuration) |
||||
|
{ |
||||
|
// TODO: Configure TickerQ here when the package becomes available |
||||
|
// This would typically involve setting up the database connection, |
||||
|
// configuring the scheduler options, and setting up the dashboard |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
2. You can configure the ABP TickerQ integration options: |
||||
|
|
||||
|
````csharp |
||||
|
Configure<AbpBackgroundWorkerTickerQOptions>(options => |
||||
|
{ |
||||
|
options.IsAutoRegisterEnabled = true; // Auto-register TickerQ workers |
||||
|
options.DefaultCronExpression = "0 * * ? * *"; // Default: every minute |
||||
|
options.DefaultMaxRetryAttempts = 3; // Default retry attempts |
||||
|
options.DefaultPriority = 0; // Default priority |
||||
|
}); |
||||
|
```` |
||||
|
|
||||
|
## Create a Background Worker |
||||
|
|
||||
|
`TickerQBackgroundWorkerBase` is an easy way to create a background worker. |
||||
|
|
||||
|
````csharp |
||||
|
public class MyLogWorker : TickerQBackgroundWorkerBase |
||||
|
{ |
||||
|
public MyLogWorker() |
||||
|
{ |
||||
|
JobId = nameof(MyLogWorker); |
||||
|
CronExpression = "0 */10 * ? * *"; // Every 10 minutes |
||||
|
Priority = 1; // Higher priority |
||||
|
MaxRetryAttempts = 5; // Retry up to 5 times on failure |
||||
|
} |
||||
|
|
||||
|
public override Task DoWorkAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
Logger.LogInformation("Executed MyLogWorker with TickerQ!"); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
### Properties |
||||
|
|
||||
|
* **JobId** - A unique identifier for the job (optional, defaults to the class name) |
||||
|
* **CronExpression** - A CRON expression for scheduling (see [CRON expression](https://en.wikipedia.org/wiki/Cron#CRON_expression)) |
||||
|
* **Priority** - Job priority (higher values = higher priority, default is 0) |
||||
|
* **MaxRetryAttempts** - Maximum number of retry attempts on failure (default is 3) |
||||
|
* **AutoRegister** - Whether to automatically register this worker (default is true) |
||||
|
|
||||
|
> You can directly implement the `ITickerQBackgroundWorker` interface, but `TickerQBackgroundWorkerBase` provides useful properties like Logger and service access. |
||||
|
|
||||
|
## Register Background Workers |
||||
|
|
||||
|
TickerQ background workers are automatically registered if `AutoRegister` is `true` (default). However, you can also manually register them: |
||||
|
|
||||
|
````csharp |
||||
|
[DependsOn(typeof(AbpBackgroundWorkersTickerQModule))] |
||||
|
public class MyModule : AbpModule |
||||
|
{ |
||||
|
public override async Task OnApplicationInitializationAsync( |
||||
|
ApplicationInitializationContext context) |
||||
|
{ |
||||
|
await context.AddBackgroundWorkerAsync<MyLogWorker>(); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
## Migrating from Other Background Worker Implementations |
||||
|
|
||||
|
TickerQ integration provides adapters for existing background workers: |
||||
|
|
||||
|
### From Default Background Workers |
||||
|
|
||||
|
Existing `AsyncPeriodicBackgroundWorkerBase` and `PeriodicBackgroundWorkerBase` workers will automatically work with TickerQ through the adapter system. The adapter will convert timer periods to appropriate cron expressions. |
||||
|
|
||||
|
### From Quartz or Hangfire |
||||
|
|
||||
|
When migrating from Quartz or Hangfire, you can: |
||||
|
|
||||
|
1. Keep existing workers unchanged (they'll work through adapters) |
||||
|
2. Gradually migrate to `TickerQBackgroundWorkerBase` for better performance and features |
||||
|
3. Use the native TickerQ features like source generator optimizations |
||||
|
|
||||
|
## Dashboard Integration |
||||
|
|
||||
|
TickerQ provides a real-time dashboard for monitoring background jobs. To enable the dashboard: |
||||
|
|
||||
|
````csharp |
||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var app = context.GetApplicationBuilder(); |
||||
|
|
||||
|
// ... others |
||||
|
|
||||
|
// TODO: Add TickerQ dashboard integration when available |
||||
|
// app.UseTickerQDashboard("/tickerq"); |
||||
|
|
||||
|
app.UseConfiguredEndpoints(); |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
## Advanced Features |
||||
|
|
||||
|
### Source Generator Optimizations |
||||
|
|
||||
|
TickerQ uses source generators to eliminate reflection and improve performance. When using TickerQ-specific features, your jobs will benefit from: |
||||
|
|
||||
|
- Compile-time job registration |
||||
|
- Zero-allocation job execution |
||||
|
- Optimized serialization |
||||
|
|
||||
|
### EF Core Integration |
||||
|
|
||||
|
TickerQ provides native Entity Framework Core integration for job persistence: |
||||
|
|
||||
|
````csharp |
||||
|
public class MyEfCoreWorker : TickerQBackgroundWorkerBase |
||||
|
{ |
||||
|
private readonly IRepository<MyEntity> _repository; |
||||
|
|
||||
|
public MyEfCoreWorker(IRepository<MyEntity> repository) |
||||
|
{ |
||||
|
_repository = repository; |
||||
|
JobId = nameof(MyEfCoreWorker); |
||||
|
CronExpression = "0 0 2 ? * *"; // Daily at 2 AM |
||||
|
} |
||||
|
|
||||
|
public override async Task DoWorkAsync(CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
// Work with EF Core entities |
||||
|
var entities = await _repository.GetListAsync(cancellationToken: cancellationToken); |
||||
|
|
||||
|
// Process entities... |
||||
|
|
||||
|
Logger.LogInformation("Processed {Count} entities", entities.Count); |
||||
|
} |
||||
|
} |
||||
|
```` |
||||
|
|
||||
|
## See Also |
||||
|
|
||||
|
* [Background Workers](../background-workers) |
||||
|
* [Background Jobs](../background-jobs) |
||||
|
* [Quartz Background Worker Manager](./quartz.md) |
||||
|
* [Hangfire Background Worker Manager](./hangfire.md) |
||||
@ -0,0 +1,3 @@ |
|||||
|
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
||||
|
<ConfigureAwait ContinueOnCapturedContext="false" /> |
||||
|
</Weavers> |
||||
@ -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> |
||||
@ -0,0 +1,25 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>netstandard2.0;netstandard2.1;net8.0;net9.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" /> |
||||
|
<!-- Note: This would need to reference the actual TickerQ package when it becomes available --> |
||||
|
<!-- <PackageReference Include="TickerQ" Version="*" /> --> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,31 @@ |
|||||
|
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; |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
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))] |
||||
|
public class AbpBackgroundWorkersTickerQModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
// Register TickerQ-specific services
|
||||
|
// 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(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,44 @@ |
|||||
|
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); |
||||
|
} |
||||
@ -0,0 +1,46 @@ |
|||||
|
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); |
||||
|
} |
||||
@ -0,0 +1,131 @@ |
|||||
|
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."); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,128 @@ |
|||||
|
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; |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue