mirror of https://github.com/abpframework/abp.git
25 changed files with 255 additions and 794 deletions
@ -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,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> |
|||
@ -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); |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
@ -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. |
|||
@ -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>();
|
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
} |
|||
@ -1,3 +0,0 @@ |
|||
{ |
|||
"role": "lib.framework" |
|||
} |
|||
@ -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 |
|||
} |
|||
] |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
|
|||
} |
|||
|
|||
@ -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); |
|||
} |
|||
@ -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); |
|||
} |
|||
@ -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."); |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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,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> |
|||
@ -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()); |
|||
}); |
|||
|
|||
} |
|||
} |
|||
Loading…
Reference in new issue