mirror of https://github.com/abpframework/abp.git
committed by
GitHub
41 changed files with 1206 additions and 8 deletions
@ -0,0 +1,125 @@ |
|||||
|
# TickerQ Background Job Manager |
||||
|
|
||||
|
[TickerQ](https://tickerq.net/) is a fast, reflection-free background task scheduler for .NET — built with source generators, EF Core integration, cron + time-based execution, and a real-time dashboard. You can integrate TickerQ with the ABP to use it instead of the [default background job manager](../background-jobs). In this way, you can use the same background job API for TickerQ and your code will be independent of TickerQ. If you like, you can directly use TickerQ's API, too. |
||||
|
|
||||
|
> See the [background jobs document](../background-jobs) to learn how to use the background job system. This document only shows how to install and configure the TickerQ integration. |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
It is suggested to use the [ABP CLI](../../../cli) to install this package. |
||||
|
|
||||
|
### Using the ABP CLI |
||||
|
|
||||
|
Open a command line window in the folder of the project (.csproj file) and type the following command: |
||||
|
|
||||
|
````bash |
||||
|
abp add-package Volo.Abp.BackgroundJobs.TickerQ |
||||
|
```` |
||||
|
|
||||
|
> If you haven't done it yet, you first need to install the [ABP CLI](../../../cli). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundJobs.TickerQ). |
||||
|
|
||||
|
## Configuration |
||||
|
|
||||
|
### AddTickerQ |
||||
|
|
||||
|
You can call the `AddTickerQ` extension method in the `ConfigureServices` method of your module to configure TickerQ services: |
||||
|
|
||||
|
> This is optional. ABP will automatically register TickerQ services. |
||||
|
|
||||
|
```csharp |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddTickerQ(x => |
||||
|
{ |
||||
|
// Configure TickerQ options here |
||||
|
}); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### UseAbpTickerQ |
||||
|
|
||||
|
You need to call the `UseAbpTickerQ` extension method instead of `AddTickerQ` in the `OnApplicationInitialization` method of your module: |
||||
|
|
||||
|
```csharp |
||||
|
// (default: TickerQStartMode.Immediate) |
||||
|
app.UseAbpTickerQ(startMode: ...); |
||||
|
``` |
||||
|
|
||||
|
### AbpBackgroundJobsTickerQOptions |
||||
|
|
||||
|
You can configure the `TimeTicker` properties for specific jobs. For example, you can change `Priority`, `Retries` and `RetryIntervals` properties as shown below: |
||||
|
|
||||
|
```csharp |
||||
|
Configure<AbpBackgroundJobsTickerQOptions>(options => |
||||
|
{ |
||||
|
options.AddJobConfiguration<MyBackgroundJob>(new AbpBackgroundJobsTimeTickerConfiguration() |
||||
|
{ |
||||
|
Retries = 3, |
||||
|
RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min |
||||
|
Priority = TickerTaskPriority.High |
||||
|
|
||||
|
// Optional batching |
||||
|
//BatchParent = Guid.Parse("...."), |
||||
|
//BatchRunCondition = BatchRunCondition.OnSuccess |
||||
|
}); |
||||
|
|
||||
|
options.AddJobConfiguration<MyBackgroundJob2>(new AbpBackgroundJobsTimeTickerConfiguration() |
||||
|
{ |
||||
|
Retries = 5, |
||||
|
RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min |
||||
|
Priority = TickerTaskPriority.Normal |
||||
|
}); |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
### Add your own TickerQ Background Jobs Definitions |
||||
|
|
||||
|
ABP will handle the TickerQ job definitions by `AbpTickerQFunctionProvider` service. You shouldn't use `TickerFunction` to add your own job definitions. You can inject and use the `AbpTickerQFunctionProvider` to add your own definitions and use `ITimeTickerManager<TimeTicker>` or `ICronTickerManager<CronTicker>` to manage the jobs. |
||||
|
|
||||
|
For example, you can add a `CleanupJobs` job definition in the `OnPreApplicationInitializationAsync` method of your module: |
||||
|
|
||||
|
```csharp |
||||
|
public class CleanupJobs |
||||
|
{ |
||||
|
public async Task CleanupLogsAsync(TickerFunctionContext<string> tickerContext, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var logFileName = tickerContext.Request; |
||||
|
Console.WriteLine($"Cleaning up log file: {logFileName} at {DateTime.Now}"); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
```csharp |
||||
|
public override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService<AbpTickerQFunctionProvider>(); |
||||
|
abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => |
||||
|
{ |
||||
|
var service = new CleanupJobs(); // Or get it from the serviceProvider |
||||
|
var request = await TickerRequestProvider.GetRequestAsync<string>(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); |
||||
|
var genericContext = new TickerFunctionContext<string>(tickerFunctionContext, request); |
||||
|
await service.CleanupLogsAsync(genericContext, cancellationToken); |
||||
|
}))); |
||||
|
abpTickerQFunctionProvider.RequestTypes.TryAdd(nameof(CleanupJobs), (typeof(string).FullName, typeof(string))); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
And then you can add a job by using the `ITimeTickerManager<TimeTicker>`: |
||||
|
|
||||
|
```csharp |
||||
|
var timeTickerManager = context.ServiceProvider.GetRequiredService<ITimeTickerManager<TimeTicker>>(); |
||||
|
await timeTickerManager.AddAsync(new TimeTicker |
||||
|
{ |
||||
|
Function = nameof(CleanupJobs), |
||||
|
ExecutionTime = DateTime.UtcNow.AddSeconds(5), |
||||
|
Request = TickerHelper.CreateTickerRequest<string>("cleanup_example_file.txt"), |
||||
|
Retries = 3, |
||||
|
RetryIntervals = new[] { 30, 60, 120 }, // Retry after 30s, 60s, then 2min |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
### TickerQ Dashboard and EF Core Integration |
||||
|
|
||||
|
You can install the [TickerQ dashboard](https://tickerq.net/setup/dashboard.html) and [Entity Framework Core](https://tickerq.net/setup/tickerq-ef-core.html) integration by its documentation. There is no specific configuration needed for the ABP integration. |
||||
|
|
||||
@ -0,0 +1,119 @@ |
|||||
|
# TickerQ Background Worker Manager |
||||
|
|
||||
|
[TickerQ](https://tickerq.net/) is a fast, reflection-free background task scheduler for .NET — built with source generators, EF Core integration, cron + time-based execution, and a real-time dashboard. You can integrate TickerQ with the ABP to use it instead of the [default background worker manager](../background-workers). |
||||
|
|
||||
|
## Installation |
||||
|
|
||||
|
It is suggested to use the [ABP CLI](../../../cli) to install this package. |
||||
|
|
||||
|
### Using the ABP CLI |
||||
|
|
||||
|
Open a command line window in the folder of the project (.csproj file) and type the following command: |
||||
|
|
||||
|
````bash |
||||
|
abp add-package Volo.Abp.BackgroundWorkers.TickerQ |
||||
|
```` |
||||
|
|
||||
|
> If you haven't done it yet, you first need to install the [ABP CLI](../../../cli). For other installation options, see [the package description page](https://abp.io/package-detail/Volo.Abp.BackgroundWorkers.TickerQ). |
||||
|
|
||||
|
## Configuration |
||||
|
|
||||
|
### AddTickerQ |
||||
|
|
||||
|
You can call the `AddTickerQ` extension method in the `ConfigureServices` method of your module to configure TickerQ services: |
||||
|
|
||||
|
> This is optional. ABP will automatically register TickerQ services. |
||||
|
|
||||
|
```csharp |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddTickerQ(x => |
||||
|
{ |
||||
|
// Configure TickerQ options here |
||||
|
}); |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
### UseAbpTickerQ |
||||
|
|
||||
|
You need to call the `UseAbpTickerQ` extension method instead of `AddTickerQ` in the `OnApplicationInitialization` method of your module: |
||||
|
|
||||
|
```csharp |
||||
|
// (default: TickerQStartMode.Immediate) |
||||
|
app.UseAbpTickerQ(startMode: ...); |
||||
|
``` |
||||
|
|
||||
|
### AbpBackgroundWorkersTickerQOptions |
||||
|
|
||||
|
You can configure the `CronTicker` properties for specific jobs. For example, Change `Priority`, `Retries` and `RetryIntervals` properties: |
||||
|
|
||||
|
```csharp |
||||
|
Configure<AbpBackgroundWorkersTickerQOptions>(options => |
||||
|
{ |
||||
|
options.AddConfiguration<MyBackgroundWorker>(new AbpBackgroundWorkersCronTickerConfiguration() |
||||
|
{ |
||||
|
Retries = 3, |
||||
|
RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min, |
||||
|
Priority = TickerTaskPriority.High |
||||
|
}); |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
### Add your own TickerQ Background Worker Definitions |
||||
|
|
||||
|
ABP will handle the TickerQ job definitions by `AbpTickerQFunctionProvider` service. You shouldn't use `TickerFunction` to add your own job definitions. You can inject and use the `AbpTickerQFunctionProvider` to add your own definitions and use `ITimeTickerManager<TimeTicker>` or `ICronTickerManager<CronTicker>` to manage the jobs. |
||||
|
|
||||
|
For example, you can add a `CleanupJobs` job definition in the `OnPreApplicationInitializationAsync` method of your module: |
||||
|
|
||||
|
```csharp |
||||
|
public class CleanupJobs |
||||
|
{ |
||||
|
public async Task CleanupLogsAsync(TickerFunctionContext<string> tickerContext, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var logFileName = tickerContext.Request; |
||||
|
Console.WriteLine($"Cleaning up log file: {logFileName} at {DateTime.Now}"); |
||||
|
} |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
```csharp |
||||
|
public override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService<AbpTickerQFunctionProvider>(); |
||||
|
abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => |
||||
|
{ |
||||
|
var service = new CleanupJobs(); // Or get it from the serviceProvider |
||||
|
var request = await TickerRequestProvider.GetRequestAsync<string>(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); |
||||
|
var genericContext = new TickerFunctionContext<string>(tickerFunctionContext, request); |
||||
|
await service.CleanupLogsAsync(genericContext, cancellationToken); |
||||
|
}))); |
||||
|
abpTickerQFunctionProvider.RequestTypes.TryAdd(nameof(CleanupJobs), (typeof(string).FullName, typeof(string))); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
``` |
||||
|
|
||||
|
And then you can add a job by using the `ICronTickerManager<CronTicker>`: |
||||
|
|
||||
|
```csharp |
||||
|
var cronTickerManager = context.ServiceProvider.GetRequiredService<ICronTickerManager<CronTicker>>(); |
||||
|
await cronTickerManager.AddAsync(new CronTicker |
||||
|
{ |
||||
|
Function = nameof(CleanupJobs), |
||||
|
Expression = "0 */6 * * *", // Every 6 hours |
||||
|
Request = TickerHelper.CreateTickerRequest<string>("cleanup_example_file.txt"), |
||||
|
Retries = 2, |
||||
|
RetryIntervals = new[] { 60, 300 } |
||||
|
}); |
||||
|
``` |
||||
|
|
||||
|
You can specify a cron expression instead of use `ICronTickerManager<CronTicker>` to add a worker: |
||||
|
|
||||
|
```csharp |
||||
|
abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => |
||||
|
{ |
||||
|
var service = new CleanupJobs(); |
||||
|
var request = await TickerRequestProvider.GetRequestAsync<string>(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); |
||||
|
var genericContext = new TickerFunctionContext<string>(tickerFunctionContext, request); |
||||
|
await service.CleanupLogsAsync(genericContext, cancellationToken); |
||||
|
}))); |
||||
|
``` |
||||
@ -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,75 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Reflection; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using TickerQ.Utilities; |
||||
|
using TickerQ.Utilities.Enums; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.TickerQ; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs.TickerQ; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpBackgroundJobsAbstractionsModule), |
||||
|
typeof(AbpTickerQModule) |
||||
|
)] |
||||
|
public class AbpBackgroundJobsTickerQModule : AbpModule |
||||
|
{ |
||||
|
private static readonly MethodInfo GetTickerFunctionDelegateMethod = |
||||
|
typeof(AbpBackgroundJobsTickerQModule).GetMethod(nameof(GetTickerFunctionDelegate), BindingFlags.NonPublic | BindingFlags.Static)!; |
||||
|
|
||||
|
public override void OnApplicationInitialization(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var abpBackgroundJobOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>(); |
||||
|
var abpBackgroundJobsTickerQOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundJobsTickerQOptions>>(); |
||||
|
var tickerFunctionDelegates = new Dictionary<string, (string, TickerTaskPriority, TickerFunctionDelegate)>(); |
||||
|
var requestTypes = new Dictionary<string, (string, Type)>(); |
||||
|
foreach (var jobConfiguration in abpBackgroundJobOptions.Value.GetJobs()) |
||||
|
{ |
||||
|
var genericMethod = GetTickerFunctionDelegateMethod.MakeGenericMethod(jobConfiguration.ArgsType); |
||||
|
var tickerFunctionDelegate = (TickerFunctionDelegate)genericMethod.Invoke(null, [jobConfiguration.ArgsType])!; |
||||
|
var config = abpBackgroundJobsTickerQOptions.Value.GetConfigurationOrNull(jobConfiguration.JobType); |
||||
|
tickerFunctionDelegates.TryAdd(jobConfiguration.JobName, (string.Empty, config?.Priority ?? TickerTaskPriority.Normal, tickerFunctionDelegate)); |
||||
|
requestTypes.TryAdd(jobConfiguration.JobName, (jobConfiguration.ArgsType.FullName, jobConfiguration.ArgsType)!); |
||||
|
} |
||||
|
|
||||
|
var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService<AbpTickerQFunctionProvider>(); |
||||
|
foreach (var functionDelegate in tickerFunctionDelegates) |
||||
|
{ |
||||
|
abpTickerQFunctionProvider.Functions.TryAdd(functionDelegate.Key, functionDelegate.Value); |
||||
|
} |
||||
|
|
||||
|
foreach (var requestType in requestTypes) |
||||
|
{ |
||||
|
abpTickerQFunctionProvider.RequestTypes.TryAdd(requestType.Key, requestType.Value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static TickerFunctionDelegate GetTickerFunctionDelegate<TArgs>(Type argsType) |
||||
|
{ |
||||
|
return async (cancellationToken, serviceProvider, context) => |
||||
|
{ |
||||
|
var options = serviceProvider.GetRequiredService<IOptions<AbpBackgroundJobOptions>>().Value; |
||||
|
if (!options.IsJobExecutionEnabled) |
||||
|
{ |
||||
|
throw new AbpException( |
||||
|
"Background job execution is disabled. " + |
||||
|
"This method should not be called! " + |
||||
|
"If you want to enable the background job execution, " + |
||||
|
$"set {nameof(AbpBackgroundJobOptions)}.{nameof(AbpBackgroundJobOptions.IsJobExecutionEnabled)} to true! " + |
||||
|
"If you've intentionally disabled job execution and this seems a bug, please report it." |
||||
|
); |
||||
|
} |
||||
|
|
||||
|
using (var scope = serviceProvider.CreateScope()) |
||||
|
{ |
||||
|
var jobExecuter = serviceProvider.GetRequiredService<IBackgroundJobExecuter>(); |
||||
|
var args = await TickerRequestProvider.GetRequestAsync<TArgs>(serviceProvider, context.Id, context.Type); |
||||
|
var jobType = options.GetJob(typeof(TArgs)).JobType; |
||||
|
var jobExecutionContext = new JobExecutionContext(scope.ServiceProvider, jobType, args!, cancellationToken: cancellationToken); |
||||
|
await jobExecuter.ExecuteAsync(jobExecutionContext); |
||||
|
} |
||||
|
}; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs.TickerQ; |
||||
|
|
||||
|
public class AbpBackgroundJobsTickerQOptions |
||||
|
{ |
||||
|
private readonly Dictionary<Type, AbpBackgroundJobsTimeTickerConfiguration> _configurations; |
||||
|
|
||||
|
public AbpBackgroundJobsTickerQOptions() |
||||
|
{ |
||||
|
_configurations = new Dictionary<Type, AbpBackgroundJobsTimeTickerConfiguration>(); |
||||
|
} |
||||
|
|
||||
|
public void AddConfiguration<TJob>(AbpBackgroundJobsTimeTickerConfiguration configuration) |
||||
|
{ |
||||
|
AddConfiguration(typeof(TJob), configuration); |
||||
|
} |
||||
|
|
||||
|
public void AddConfiguration(Type jobType, AbpBackgroundJobsTimeTickerConfiguration configuration) |
||||
|
{ |
||||
|
_configurations[jobType] = configuration; |
||||
|
} |
||||
|
|
||||
|
public AbpBackgroundJobsTimeTickerConfiguration? GetConfigurationOrNull<TJob>() |
||||
|
{ |
||||
|
return GetConfigurationOrNull(typeof(TJob)); |
||||
|
} |
||||
|
|
||||
|
public AbpBackgroundJobsTimeTickerConfiguration? GetConfigurationOrNull(Type jobType) |
||||
|
{ |
||||
|
return _configurations.GetValueOrDefault(jobType); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,17 @@ |
|||||
|
using System; |
||||
|
using TickerQ.Utilities.Enums; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs.TickerQ; |
||||
|
|
||||
|
public class AbpBackgroundJobsTimeTickerConfiguration |
||||
|
{ |
||||
|
public int? Retries { get; set; } |
||||
|
|
||||
|
public int[]? RetryIntervals { get; set; } |
||||
|
|
||||
|
public TickerTaskPriority? Priority { get; set; } |
||||
|
|
||||
|
public Guid? BatchParent { get; set; } |
||||
|
|
||||
|
public BatchRunCondition? BatchRunCondition { get; set; } |
||||
|
} |
||||
@ -0,0 +1,51 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using TickerQ.Utilities; |
||||
|
using TickerQ.Utilities.Interfaces.Managers; |
||||
|
using TickerQ.Utilities.Models.Ticker; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs.TickerQ; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class AbpTickerQBackgroundJobManager : IBackgroundJobManager, ITransientDependency |
||||
|
{ |
||||
|
protected ITimeTickerManager<TimeTicker> TimeTickerManager { get; } |
||||
|
protected AbpBackgroundJobOptions Options { get; } |
||||
|
protected AbpBackgroundJobsTickerQOptions TickerQOptions { get; } |
||||
|
|
||||
|
public AbpTickerQBackgroundJobManager( |
||||
|
ITimeTickerManager<TimeTicker> timeTickerManager, |
||||
|
IOptions<AbpBackgroundJobOptions> options, |
||||
|
IOptions<AbpBackgroundJobsTickerQOptions> tickerQOptions) |
||||
|
{ |
||||
|
TimeTickerManager = timeTickerManager; |
||||
|
Options = options.Value; |
||||
|
TickerQOptions = tickerQOptions.Value; |
||||
|
} |
||||
|
|
||||
|
public virtual async Task<string> EnqueueAsync<TArgs>(TArgs args, BackgroundJobPriority priority = BackgroundJobPriority.Normal, TimeSpan? delay = null) |
||||
|
{ |
||||
|
var job = Options.GetJob(typeof(TArgs)); |
||||
|
var timeTicker = new TimeTicker |
||||
|
{ |
||||
|
Id = Guid.NewGuid(), |
||||
|
Function = job.JobName, |
||||
|
ExecutionTime = delay == null ? DateTime.UtcNow : DateTime.UtcNow.Add(delay.Value), |
||||
|
Request = TickerHelper.CreateTickerRequest<TArgs>(args), |
||||
|
}; |
||||
|
|
||||
|
var config = TickerQOptions.GetConfigurationOrNull(job.JobType); |
||||
|
if (config != null) |
||||
|
{ |
||||
|
timeTicker.Retries = config.Retries ?? timeTicker.Retries; |
||||
|
timeTicker.RetryIntervals = config.RetryIntervals ?? timeTicker.RetryIntervals; |
||||
|
timeTicker.BatchParent = config.BatchParent ?? timeTicker.BatchParent; |
||||
|
timeTicker.BatchRunCondition = config.BatchRunCondition ?? timeTicker.BatchRunCondition; |
||||
|
} |
||||
|
|
||||
|
var result = await TimeTickerManager.AddAsync(timeTicker); |
||||
|
return !result.IsSucceded ? timeTicker.Id.ToString() : result.Result.Id.ToString(); |
||||
|
} |
||||
|
} |
||||
@ -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.BackgroundWorkers.TickerQ</AssemblyName> |
||||
|
<PackageId>Volo.Abp.BackgroundWorkers.TickerQ</PackageId> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.BackgroundWorkers\Volo.Abp.BackgroundWorkers.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.TickerQ\Volo.Abp.TickerQ.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,12 @@ |
|||||
|
using TickerQ.Utilities.Enums; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.TickerQ; |
||||
|
|
||||
|
public class AbpBackgroundWorkersCronTickerConfiguration |
||||
|
{ |
||||
|
public int? Retries { get; set; } |
||||
|
|
||||
|
public int[]? RetryIntervals { get; set; } |
||||
|
|
||||
|
public TickerTaskPriority? Priority { get; set; } |
||||
|
} |
||||
@ -0,0 +1,37 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using TickerQ.Utilities.Interfaces.Managers; |
||||
|
using TickerQ.Utilities.Models.Ticker; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.TickerQ; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.TickerQ; |
||||
|
|
||||
|
[DependsOn(typeof(AbpBackgroundWorkersModule), typeof(AbpTickerQModule))] |
||||
|
public class AbpBackgroundWorkersTickerQModule : AbpModule |
||||
|
{ |
||||
|
public override async Task OnPostApplicationInitializationAsync(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var abpTickerQBackgroundWorkersProvider = context.ServiceProvider.GetRequiredService<AbpTickerQBackgroundWorkersProvider>(); |
||||
|
var cronTickerManager = context.ServiceProvider.GetRequiredService<ICronTickerManager<CronTicker>>(); |
||||
|
var abpBackgroundWorkersTickerQOptions = context.ServiceProvider.GetRequiredService<IOptions<AbpBackgroundWorkersTickerQOptions>>().Value; |
||||
|
foreach (var backgroundWorker in abpTickerQBackgroundWorkersProvider.BackgroundWorkers) |
||||
|
{ |
||||
|
var cronTicker = new CronTicker |
||||
|
{ |
||||
|
Function = backgroundWorker.Value.Function, |
||||
|
Expression = backgroundWorker.Value.CronExpression |
||||
|
}; |
||||
|
|
||||
|
var config = abpBackgroundWorkersTickerQOptions.GetConfigurationOrNull(backgroundWorker.Value.WorkerType); |
||||
|
if (config != null) |
||||
|
{ |
||||
|
cronTicker.Retries = config.Retries ?? cronTicker.Retries; |
||||
|
cronTicker.RetryIntervals = config.RetryIntervals ?? cronTicker.RetryIntervals; |
||||
|
} |
||||
|
|
||||
|
await cronTickerManager.AddAsync(cronTicker); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,34 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.TickerQ; |
||||
|
|
||||
|
public class AbpBackgroundWorkersTickerQOptions |
||||
|
{ |
||||
|
private readonly Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration> _onfigurations; |
||||
|
|
||||
|
public AbpBackgroundWorkersTickerQOptions() |
||||
|
{ |
||||
|
_onfigurations = new Dictionary<Type, AbpBackgroundWorkersCronTickerConfiguration>(); |
||||
|
} |
||||
|
|
||||
|
public void AddConfiguration<TWorker>(AbpBackgroundWorkersCronTickerConfiguration configuration) |
||||
|
{ |
||||
|
AddConfiguration(typeof(TWorker), configuration); |
||||
|
} |
||||
|
|
||||
|
public void AddConfiguration(Type workerType, AbpBackgroundWorkersCronTickerConfiguration configuration) |
||||
|
{ |
||||
|
_onfigurations[workerType] = configuration; |
||||
|
} |
||||
|
|
||||
|
public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull<TJob>() |
||||
|
{ |
||||
|
return GetConfigurationOrNull(typeof(TJob)); |
||||
|
} |
||||
|
|
||||
|
public AbpBackgroundWorkersCronTickerConfiguration? GetConfigurationOrNull(Type workerType) |
||||
|
{ |
||||
|
return _onfigurations.GetValueOrDefault(workerType); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,105 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.Extensions.Options; |
||||
|
using TickerQ.Utilities.Enums; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
using Volo.Abp.DynamicProxy; |
||||
|
using Volo.Abp.TickerQ; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.TickerQ; |
||||
|
|
||||
|
[Dependency(ReplaceServices = true)] |
||||
|
public class AbpTickerQBackgroundWorkerManager : BackgroundWorkerManager, ISingletonDependency |
||||
|
{ |
||||
|
protected AbpTickerQFunctionProvider AbpTickerQFunctionProvider { get; } |
||||
|
protected AbpTickerQBackgroundWorkersProvider AbpTickerQBackgroundWorkersProvider { get; } |
||||
|
protected AbpBackgroundWorkersTickerQOptions Options { get; } |
||||
|
|
||||
|
public AbpTickerQBackgroundWorkerManager( |
||||
|
AbpTickerQFunctionProvider abpTickerQFunctionProvider, |
||||
|
AbpTickerQBackgroundWorkersProvider abpTickerQBackgroundWorkersProvider, |
||||
|
IOptions<AbpBackgroundWorkersTickerQOptions> options) |
||||
|
{ |
||||
|
AbpTickerQFunctionProvider = abpTickerQFunctionProvider; |
||||
|
AbpTickerQBackgroundWorkersProvider = abpTickerQBackgroundWorkersProvider; |
||||
|
Options = options.Value; |
||||
|
} |
||||
|
|
||||
|
public override async Task AddAsync(IBackgroundWorker worker, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
if (worker is AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase) |
||||
|
{ |
||||
|
int? period = null; |
||||
|
string? cronExpression = null; |
||||
|
|
||||
|
if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase) |
||||
|
{ |
||||
|
period = asyncPeriodicBackgroundWorkerBase.Period; |
||||
|
cronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression; |
||||
|
} |
||||
|
else if (worker is PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase) |
||||
|
{ |
||||
|
period = periodicBackgroundWorkerBase.Period; |
||||
|
cronExpression = periodicBackgroundWorkerBase.CronExpression; |
||||
|
} |
||||
|
|
||||
|
if (period == null && cronExpression.IsNullOrWhiteSpace()) |
||||
|
{ |
||||
|
throw new AbpException($"Both 'Period' and 'CronExpression' are not set for {worker.GetType().FullName}. You must set at least one of them."); |
||||
|
} |
||||
|
|
||||
|
cronExpression = cronExpression ?? GetCron(period!.Value); |
||||
|
var name = BackgroundWorkerNameAttribute.GetNameOrNull(worker.GetType()) ?? worker.GetType().FullName; |
||||
|
|
||||
|
var config = Options.GetConfigurationOrNull(ProxyHelper.GetUnProxiedType(worker)); |
||||
|
AbpTickerQFunctionProvider.Functions.TryAdd(name!, (string.Empty, config?.Priority ?? TickerTaskPriority.LongRunning, async (tickerQCancellationToken, serviceProvider, tickerFunctionContext) => |
||||
|
{ |
||||
|
var workerInvoker = new AbpTickerQPeriodicBackgroundWorkerInvoker(worker, serviceProvider); |
||||
|
await workerInvoker.DoWorkAsync(tickerFunctionContext, tickerQCancellationToken); |
||||
|
})); |
||||
|
|
||||
|
AbpTickerQBackgroundWorkersProvider.BackgroundWorkers.Add(name!, new AbpTickerQCronBackgroundWorker |
||||
|
{ |
||||
|
Function = name!, |
||||
|
CronExpression = cronExpression, |
||||
|
WorkerType = ProxyHelper.GetUnProxiedType(worker) |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
await base.AddAsync(worker, cancellationToken); |
||||
|
} |
||||
|
|
||||
|
protected virtual string GetCron(int period) |
||||
|
{ |
||||
|
var time = TimeSpan.FromMilliseconds(period); |
||||
|
if (time.TotalMinutes < 1) |
||||
|
{ |
||||
|
// Less than 1 minute — 5-field cron doesn't support seconds, so run every minute
|
||||
|
return "* * * * *"; |
||||
|
} |
||||
|
|
||||
|
if (time.TotalMinutes < 60) |
||||
|
{ |
||||
|
// Run every N minutes
|
||||
|
var minutes = (int)Math.Round(time.TotalMinutes); |
||||
|
return $"*/{minutes} * * * *"; |
||||
|
} |
||||
|
|
||||
|
if (time.TotalHours < 24) |
||||
|
{ |
||||
|
// Run every N hours
|
||||
|
var hours = (int)Math.Round(time.TotalHours); |
||||
|
return $"0 */{hours} * * *"; |
||||
|
} |
||||
|
|
||||
|
if (time.TotalDays <= 31) |
||||
|
{ |
||||
|
// Run every N days
|
||||
|
var days = (int)Math.Round(time.TotalDays); |
||||
|
return $"0 0 */{days} * *"; |
||||
|
} |
||||
|
|
||||
|
throw new AbpException($"Cannot convert period: {period} to cron expression."); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,14 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.TickerQ; |
||||
|
|
||||
|
public class AbpTickerQBackgroundWorkersProvider : ISingletonDependency |
||||
|
{ |
||||
|
public Dictionary<string, AbpTickerQCronBackgroundWorker> BackgroundWorkers { get;} |
||||
|
|
||||
|
public AbpTickerQBackgroundWorkersProvider() |
||||
|
{ |
||||
|
BackgroundWorkers = new Dictionary<string, AbpTickerQCronBackgroundWorker>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,12 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.TickerQ; |
||||
|
|
||||
|
public class AbpTickerQCronBackgroundWorker |
||||
|
{ |
||||
|
public string Function { get; set; } = null!; |
||||
|
|
||||
|
public string CronExpression { get; set; } = null!; |
||||
|
|
||||
|
public Type WorkerType { get; set; } = null!; |
||||
|
} |
||||
@ -0,0 +1,73 @@ |
|||||
|
using System; |
||||
|
using System.Linq.Expressions; |
||||
|
using System.Reflection; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using TickerQ.Utilities.Models; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundWorkers.TickerQ; |
||||
|
|
||||
|
public class AbpTickerQPeriodicBackgroundWorkerInvoker |
||||
|
{ |
||||
|
private readonly Func<AsyncPeriodicBackgroundWorkerBase, PeriodicBackgroundWorkerContext, Task>? _doWorkAsyncDelegate; |
||||
|
private readonly Action<PeriodicBackgroundWorkerBase, PeriodicBackgroundWorkerContext>? _doWorkDelegate; |
||||
|
|
||||
|
protected IBackgroundWorker Worker { get; } |
||||
|
protected IServiceProvider ServiceProvider { get; } |
||||
|
|
||||
|
public AbpTickerQPeriodicBackgroundWorkerInvoker(IBackgroundWorker worker, IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
Worker = worker; |
||||
|
ServiceProvider = serviceProvider; |
||||
|
|
||||
|
switch (worker) |
||||
|
{ |
||||
|
case AsyncPeriodicBackgroundWorkerBase: |
||||
|
{ |
||||
|
var workerType = worker.GetType(); |
||||
|
var method = workerType.GetMethod("DoWorkAsync", BindingFlags.Instance | BindingFlags.NonPublic); |
||||
|
if (method == null) |
||||
|
{ |
||||
|
throw new AbpException($"Could not find 'DoWorkAsync' method on type '{workerType.FullName}'."); |
||||
|
} |
||||
|
|
||||
|
var instanceParam = Expression.Parameter(typeof(AsyncPeriodicBackgroundWorkerBase), "worker"); |
||||
|
var contextParam = Expression.Parameter(typeof(PeriodicBackgroundWorkerContext), "context"); |
||||
|
var call = Expression.Call(Expression.Convert(instanceParam, workerType), method, contextParam); |
||||
|
var lambda = Expression.Lambda<Func<AsyncPeriodicBackgroundWorkerBase, PeriodicBackgroundWorkerContext, Task>>(call, instanceParam, contextParam); |
||||
|
_doWorkAsyncDelegate = lambda.Compile(); |
||||
|
break; |
||||
|
} |
||||
|
case PeriodicBackgroundWorkerBase: |
||||
|
{ |
||||
|
var workerType = worker.GetType(); |
||||
|
var method = workerType.GetMethod("DoWork", BindingFlags.Instance | BindingFlags.NonPublic); |
||||
|
if (method == null) |
||||
|
{ |
||||
|
throw new AbpException($"Could not find 'DoWork' method on type '{workerType.FullName}'."); |
||||
|
} |
||||
|
|
||||
|
var instanceParam = Expression.Parameter(typeof(PeriodicBackgroundWorkerBase), "worker"); |
||||
|
var contextParam = Expression.Parameter(typeof(PeriodicBackgroundWorkerContext), "context"); |
||||
|
var call = Expression.Call(Expression.Convert(instanceParam, workerType), method, contextParam); |
||||
|
var lambda = Expression.Lambda<Action<PeriodicBackgroundWorkerBase, PeriodicBackgroundWorkerContext>>(call, instanceParam, contextParam); |
||||
|
_doWorkDelegate = lambda.Compile(); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public virtual async Task DoWorkAsync(TickerFunctionContext context, CancellationToken cancellationToken = default) |
||||
|
{ |
||||
|
var workerContext = new PeriodicBackgroundWorkerContext(ServiceProvider); |
||||
|
switch (Worker) |
||||
|
{ |
||||
|
case AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorker: |
||||
|
await _doWorkAsyncDelegate!(asyncPeriodicBackgroundWorker, workerContext); |
||||
|
break; |
||||
|
case PeriodicBackgroundWorkerBase periodicBackgroundWorker: |
||||
|
_doWorkDelegate!(periodicBackgroundWorker, workerContext); |
||||
|
break; |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -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,20 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using TickerQ.DependencyInjection; |
||||
|
using TickerQ.Utilities; |
||||
|
using TickerQ.Utilities.Enums; |
||||
|
using Volo.Abp.TickerQ; |
||||
|
|
||||
|
namespace Microsoft.AspNetCore.Builder; |
||||
|
|
||||
|
public static class AbpTickerQApplicationBuilderExtensions |
||||
|
{ |
||||
|
public static IApplicationBuilder UseAbpTickerQ(this IApplicationBuilder app, TickerQStartMode qStartMode = TickerQStartMode.Immediate) |
||||
|
{ |
||||
|
var abpTickerQFunctionProvider = app.ApplicationServices.GetRequiredService<AbpTickerQFunctionProvider>(); |
||||
|
TickerFunctionProvider.RegisterFunctions(abpTickerQFunctionProvider.Functions); |
||||
|
TickerFunctionProvider.RegisterRequestType(abpTickerQFunctionProvider.RequestTypes); |
||||
|
|
||||
|
app.UseTickerQ(qStartMode); |
||||
|
return app; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,27 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<Import Project="..\..\..\configureawait.props" /> |
||||
|
<Import Project="..\..\..\common.props" /> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<TargetFrameworks>netstandard2.1;net8.0;net9.0;net10.0</TargetFrameworks> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<WarningsAsErrors>Nullable</WarningsAsErrors> |
||||
|
<AssemblyName>Volo.Abp.TickerQ</AssemblyName> |
||||
|
<PackageId>Volo.Abp.TickerQ</PackageId> |
||||
|
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback> |
||||
|
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute> |
||||
|
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute> |
||||
|
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute> |
||||
|
<RootNamespace /> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="TickerQ" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,20 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using TickerQ.Utilities; |
||||
|
using TickerQ.Utilities.Enums; |
||||
|
using Volo.Abp.DependencyInjection; |
||||
|
|
||||
|
namespace Volo.Abp.TickerQ; |
||||
|
|
||||
|
public class AbpTickerQFunctionProvider : ISingletonDependency |
||||
|
{ |
||||
|
public Dictionary<string, (string, TickerTaskPriority, TickerFunctionDelegate)> Functions { get;} |
||||
|
|
||||
|
public Dictionary<string, (string, Type)> RequestTypes { get; } |
||||
|
|
||||
|
public AbpTickerQFunctionProvider() |
||||
|
{ |
||||
|
Functions = new Dictionary<string, (string, TickerTaskPriority, TickerFunctionDelegate)>(); |
||||
|
RequestTypes = new Dictionary<string, (string, Type)>(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,16 @@ |
|||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using TickerQ.DependencyInjection; |
||||
|
using Volo.Abp.Modularity; |
||||
|
|
||||
|
namespace Volo.Abp.TickerQ; |
||||
|
|
||||
|
public class AbpTickerQModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddTickerQ(options => |
||||
|
{ |
||||
|
options.SetInstanceIdentifier(context.Services.GetApplicationName()); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,15 @@ |
|||||
|
using System; |
||||
|
using System.Threading; |
||||
|
using System.Threading.Tasks; |
||||
|
using TickerQ.Utilities.Models; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ; |
||||
|
|
||||
|
public class CleanupJobs |
||||
|
{ |
||||
|
public async Task CleanupLogsAsync(TickerFunctionContext<string> tickerContext, CancellationToken cancellationToken) |
||||
|
{ |
||||
|
var logFileName = tickerContext.Request; |
||||
|
Console.WriteLine($"Cleaning up log file: {logFileName} at {DateTime.Now}"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,140 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using TickerQ.Dashboard.DependencyInjection; |
||||
|
using TickerQ.DependencyInjection; |
||||
|
using TickerQ.Utilities; |
||||
|
using TickerQ.Utilities.Enums; |
||||
|
using TickerQ.Utilities.Interfaces.Managers; |
||||
|
using TickerQ.Utilities.Models; |
||||
|
using TickerQ.Utilities.Models.Ticker; |
||||
|
using Volo.Abp.AspNetCore; |
||||
|
using Volo.Abp.Autofac; |
||||
|
using Volo.Abp.BackgroundJobs.DemoApp.Shared; |
||||
|
using Volo.Abp.BackgroundJobs.DemoApp.Shared.Jobs; |
||||
|
using Volo.Abp.BackgroundJobs.TickerQ; |
||||
|
using Volo.Abp.BackgroundWorkers; |
||||
|
using Volo.Abp.BackgroundWorkers.TickerQ; |
||||
|
using Volo.Abp.Modularity; |
||||
|
using Volo.Abp.TickerQ; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ; |
||||
|
|
||||
|
[DependsOn( |
||||
|
typeof(AbpBackgroundJobsTickerQModule), |
||||
|
typeof(AbpBackgroundWorkersTickerQModule), |
||||
|
typeof(DemoAppSharedModule), |
||||
|
typeof(AbpAutofacModule), |
||||
|
typeof(AbpAspNetCoreModule) |
||||
|
)] |
||||
|
public class DemoAppTickerQModule : AbpModule |
||||
|
{ |
||||
|
public override void ConfigureServices(ServiceConfigurationContext context) |
||||
|
{ |
||||
|
context.Services.AddTickerQ(options => |
||||
|
{ |
||||
|
options.UpdateMissedJobCheckDelay(TimeSpan.FromSeconds(30)); |
||||
|
|
||||
|
options.AddDashboard(x => |
||||
|
{ |
||||
|
x.BasePath = "/tickerq-dashboard"; |
||||
|
|
||||
|
x.UseHostAuthentication = true; |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpBackgroundJobsTickerQOptions>(options => |
||||
|
{ |
||||
|
options.AddConfiguration<WriteToConsoleGreenJob>(new AbpBackgroundJobsTimeTickerConfiguration() |
||||
|
{ |
||||
|
Retries = 3, |
||||
|
RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min,
|
||||
|
Priority = TickerTaskPriority.High |
||||
|
}); |
||||
|
|
||||
|
options.AddConfiguration<WriteToConsoleYellowJob>(new AbpBackgroundJobsTimeTickerConfiguration() |
||||
|
{ |
||||
|
Retries = 5, |
||||
|
RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min
|
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
Configure<AbpBackgroundWorkersTickerQOptions>(options => |
||||
|
{ |
||||
|
options.AddConfiguration<MyBackgroundWorker>(new AbpBackgroundWorkersCronTickerConfiguration() |
||||
|
{ |
||||
|
Retries = 3, |
||||
|
RetryIntervals = new[] {30, 60, 120}, // Retry after 30s, 60s, then 2min,
|
||||
|
Priority = TickerTaskPriority.High |
||||
|
}); |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
public override Task OnPreApplicationInitializationAsync(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var abpTickerQFunctionProvider = context.ServiceProvider.GetRequiredService<AbpTickerQFunctionProvider>(); |
||||
|
abpTickerQFunctionProvider.Functions.TryAdd(nameof(CleanupJobs), (string.Empty, TickerTaskPriority.Normal, new TickerFunctionDelegate(async (cancellationToken, serviceProvider, tickerFunctionContext) => |
||||
|
{ |
||||
|
var service = new CleanupJobs(); |
||||
|
var request = await TickerRequestProvider.GetRequestAsync<string>(serviceProvider, tickerFunctionContext.Id, tickerFunctionContext.Type); |
||||
|
var genericContext = new TickerFunctionContext<string>(tickerFunctionContext, request); |
||||
|
await service.CleanupLogsAsync(genericContext, cancellationToken); |
||||
|
}))); |
||||
|
abpTickerQFunctionProvider.RequestTypes.TryAdd(nameof(CleanupJobs), (typeof(string).FullName, typeof(string))); |
||||
|
return Task.CompletedTask; |
||||
|
} |
||||
|
|
||||
|
public override async Task OnApplicationInitializationAsync(ApplicationInitializationContext context) |
||||
|
{ |
||||
|
var backgroundWorkerManager = context.ServiceProvider.GetRequiredService<IBackgroundWorkerManager>(); |
||||
|
await backgroundWorkerManager.AddAsync(context.ServiceProvider.GetRequiredService<MyBackgroundWorker>()); |
||||
|
|
||||
|
var app = context.GetApplicationBuilder(); |
||||
|
app.UseAbpTickerQ(); |
||||
|
|
||||
|
var timeTickerManager = context.ServiceProvider.GetRequiredService<ITimeTickerManager<TimeTicker>>(); |
||||
|
await timeTickerManager.AddAsync(new TimeTicker |
||||
|
{ |
||||
|
Function = nameof(CleanupJobs), |
||||
|
ExecutionTime = DateTime.UtcNow.AddSeconds(5), |
||||
|
Request = TickerHelper.CreateTickerRequest<string>("cleanup_example_file.txt"), |
||||
|
Retries = 3, |
||||
|
RetryIntervals = new[] { 30, 60, 120 }, // Retry after 30s, 60s, then 2min
|
||||
|
}); |
||||
|
|
||||
|
var cronTickerManager = context.ServiceProvider.GetRequiredService<ICronTickerManager<CronTicker>>(); |
||||
|
await cronTickerManager.AddAsync(new CronTicker |
||||
|
{ |
||||
|
Function = nameof(CleanupJobs), |
||||
|
Expression = "* * * * *", // Every minute
|
||||
|
Request = TickerHelper.CreateTickerRequest<string>("cleanup_example_file.txt"), |
||||
|
Retries = 2, |
||||
|
RetryIntervals = new[] { 60, 300 } |
||||
|
}); |
||||
|
|
||||
|
app.UseRouting(); |
||||
|
app.UseEndpoints(endpoints => |
||||
|
{ |
||||
|
endpoints.MapGet("/", async httpContext => |
||||
|
{ |
||||
|
httpContext.Response.Redirect("/tickerq-dashboard", true); |
||||
|
}); |
||||
|
}); |
||||
|
|
||||
|
await CancelableBackgroundJobAsync(context.ServiceProvider); |
||||
|
} |
||||
|
|
||||
|
private async Task CancelableBackgroundJobAsync(IServiceProvider serviceProvider) |
||||
|
{ |
||||
|
var backgroundJobManager = serviceProvider.GetRequiredService<IBackgroundJobManager>(); |
||||
|
var jobId = await backgroundJobManager.EnqueueAsync(new LongRunningJobArgs { Value = "test-cancel-job" }); |
||||
|
await backgroundJobManager.EnqueueAsync(new LongRunningJobArgs { Value = "test-3" }); |
||||
|
|
||||
|
await Task.Delay(1000); |
||||
|
|
||||
|
var timeTickerManager = serviceProvider.GetRequiredService<ITimeTickerManager<TimeTicker>>(); |
||||
|
var result = await timeTickerManager.DeleteAsync(Guid.Parse(jobId)); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,22 @@ |
|||||
|
using System; |
||||
|
using System.Threading.Tasks; |
||||
|
using JetBrains.Annotations; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Volo.Abp.BackgroundWorkers; |
||||
|
using Volo.Abp.Threading; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ; |
||||
|
|
||||
|
public class MyBackgroundWorker : AsyncPeriodicBackgroundWorkerBase |
||||
|
{ |
||||
|
public MyBackgroundWorker([NotNull] AbpAsyncTimer timer, [NotNull] IServiceScopeFactory serviceScopeFactory) : base(timer, serviceScopeFactory) |
||||
|
{ |
||||
|
timer.Period = 60 * 1000; // 60 seconds
|
||||
|
CronExpression = "* * * * *"; // every minute
|
||||
|
} |
||||
|
|
||||
|
protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext) |
||||
|
{ |
||||
|
Console.WriteLine($"MyBackgroundWorker executed at {DateTime.Now}"); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
using System.Threading.Tasks; |
||||
|
using Microsoft.AspNetCore.Builder; |
||||
|
using Microsoft.Extensions.DependencyInjection; |
||||
|
using Microsoft.Extensions.Hosting; |
||||
|
|
||||
|
namespace Volo.Abp.BackgroundJobs.DemoApp.TickerQ; |
||||
|
|
||||
|
public class Program |
||||
|
{ |
||||
|
public static async Task Main(string[] args) |
||||
|
{ |
||||
|
var builder = WebApplication.CreateBuilder(args); |
||||
|
builder.Host.UseAutofac(); |
||||
|
await builder.AddApplicationAsync<DemoAppTickerQModule>(); |
||||
|
var app = builder.Build(); |
||||
|
await app.InitializeApplicationAsync(); |
||||
|
await app.RunAsync(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,13 @@ |
|||||
|
{ |
||||
|
"$schema": "http://json.schemastore.org/launchsettings.json", |
||||
|
"profiles": { |
||||
|
"Volo.Abp.BackgroundJobs.DemoApp.TickerQ": { |
||||
|
"commandName": "Project", |
||||
|
"launchBrowser": true, |
||||
|
"applicationUrl": "https://localhost:5000", |
||||
|
"environmentVariables": { |
||||
|
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,26 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
|
||||
|
<PropertyGroup> |
||||
|
<OutputType>Exe</OutputType> |
||||
|
<TargetFramework>net10.0</TargetFramework> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Autofac\Volo.Abp.Autofac.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.BackgroundJobs.TickerQ\Volo.Abp.BackgroundJobs.TickerQ.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.BackgroundWorkers.TickerQ\Volo.Abp.BackgroundWorkers.TickerQ.csproj" /> |
||||
|
<ProjectReference Include="..\Volo.Abp.BackgroundJobs.DemoApp.Shared\Volo.Abp.BackgroundJobs.DemoApp.Shared.csproj" /> |
||||
|
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.AspNetCore\Volo.Abp.AspNetCore.csproj" /> |
||||
|
<PackageReference Include="TickerQ.Dashboard" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<None Update="appsettings.json"> |
||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
||||
|
</None> |
||||
|
<None Update="appsettings.json"> |
||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory> |
||||
|
</None> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
</Project> |
||||
@ -0,0 +1,8 @@ |
|||||
|
{ |
||||
|
"Logging": { |
||||
|
"LogLevel": { |
||||
|
"Default": "Information", |
||||
|
"Microsoft.AspNetCore": "Warning" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue