Aicrosoft.Scheduling
8.0.0-beta.251110.1
dotnet add package Aicrosoft.Scheduling --version 8.0.0-beta.251110.1
NuGet\Install-Package Aicrosoft.Scheduling -Version 8.0.0-beta.251110.1
<PackageReference Include="Aicrosoft.Scheduling" Version="8.0.0-beta.251110.1" />
<PackageVersion Include="Aicrosoft.Scheduling" Version="8.0.0-beta.251110.1" />
<PackageReference Include="Aicrosoft.Scheduling" />
paket add Aicrosoft.Scheduling --version 8.0.0-beta.251110.1
#r "nuget: Aicrosoft.Scheduling, 8.0.0-beta.251110.1"
#:package Aicrosoft.Scheduling@8.0.0-beta.251110.1
#addin nuget:?package=Aicrosoft.Scheduling&version=8.0.0-beta.251110.1&prerelease
#tool nuget:?package=Aicrosoft.Scheduling&version=8.0.0-beta.251110.1&prerelease
__ _ ___ _
\ \ ___ | |__ ___ / __\_ _ ___| |_ ___ _ __ _ _
\ \/ _ \| '_ \/ __| / _\/ _` |/ __| __/ _ \| '__| | | |
/\_/ / (_) | |_) \__ \/ / | (_| | (__| || (_) | | | |_| |
\___/ \___/|_.__/|___/\/ \__,_|\___|\__\___/|_| \__, |
|___/
Keywords:
jobsFactory,jobs,task,job,routine,schedule,setup,startup,interval,cronexpression,jobagent
JobsFactory is a task scheduling framework that supports multiple types of jobs(tasks) and is easily extensible. It is written in dotnet8. It is an upgrade and refactoring of my earlier open-source projects BeesTask and SwarmTask.
An implementation example of the JobsFactory framework: JobAgent
- JobsFactory Architecture
- Supported Job Types
- Quick Start (Simple Example)
- Key Components in the Framework
- Usage
- Configuration
JobsFactory Architecture
You can visit Architecture UML to view the UML class diagram.
Supported Job Types
Before using this framework, please determine whether it is suitable for your needs based on the supported job types.
Triggered by the value configured in Job.Trigger.
| Value | Type | Example Value | Description |
|---|---|---|---|
| Setup | Installation and Upgrade | "" | Runs after startup |
| Startup | Startup | "2000" | Runs before all tasks after Setup, with a delay of 2 seconds. May run continuously. |
| Event | Event Trigger | "DirChangeEventer" | Triggered when the specified directory changes |
| Interval | Interval Execution | "00:00:20" | Runs every 20 seconds |
| Schedulable | Cron Expression | "* 0/10 * * * ?" | Cron expression: Executes every 10 minutes |
NOTE:
Job type extensions are not supported at the moment.
Quick Start (Simple Example)
Implement a Job
Code Job Description
public class Interval10Sec : Job
{
public Interval10Sec()
{
Trigger = "00:00:10";
}
public override string? WorkerName => nameof(SimapleWorker);
}
Configuration Job Description
{
"Jobs": {
// SampleJobs is the module name, used to distinguish Jobs with the same Job name in multiple plugin modules.
"SampleJobs": [
{
"name": "Interval10Sec",
"trigger": "00:00:10",
"workerName": "SimapleWorker"
},
]
}
}
Implement a Business Worker for a Job
Worker is Executing while Job Trigger (Job.WorkerName)
By using the following code, the SimpleWorker.ExecuteAsync method will be executed every 10 seconds.
[KeyedName]
public sealed class SimapleWorker(IServiceProvider serviceProvider) : Worker<JobContext>(serviceProvider)
{
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
await Task.Delay(500, cancellationToken); //mock execute 0.5 second.
await ExecuteCallbackAsync(true);// work finish callback. It's not necessary.
}
protected override async Task ExecuteCallbackAsync<TResult>(TResult? result) where TResult : default
{
if (result is true)
Logger.LogDebug($"Worker are success.");
await Task.CompletedTask;
}
}
Note the handling of the
CancellationTokensignal, which will receive a cancellation signal when the application stops.
Key Components in the Framework
To better utilize the JobFactory framework and to extend and customize it more deeply, it is essential to understand the relevant components within the framework.
You can visit Architecture UML to view the UML class diagram.
Job - Task Configuration
- A Job is a description of a task, through which you can set its job type and the business logic unit to be executed.
- There are two ways to configure it: typically in the application configuration
appsettings.jsonor in the configuration of a plugin modulemoduleAssemblyName.json. Alternatively, you can achieve the same effect by inheriting from the Job class directly in code. - Once the type of the Job's Trigger is set, do not change it.
Configuration in appsettings.json
{
"Jobs": {
// SampleJobs is the module name, used to distinguish Jobs with the same Job name in multiple plugin modules.
"SampleJobs": [
{
"name": "Interval10Sec",
"trigger": "00:00:10",
"workerName": "SimapleWorker"
},
]
}
}
Code Implementation
public class Interval10Sec : Job
{
public Interval10Sec()
{
Trigger = "00:00:10";
}
public override string? WorkerName => nameof(SimapleWorker);
}
JobContext - State and Context During Task Execution
- JobContext is the context and state corresponding to each Job, which will be persisted to the default
/states/directory after the first run. - When the task runs again, it will read the last persisted state and restore it as the current JobContext.
- Each JobContext must implement the corresponding JobFactory to create the context.
- You can also use it directly without creating a custom JobContext. You can set and retrieve different working states through the Data property.
Custom CustomJobContext
public sealed class CustomJobContext : JobContext
{
public bool Done
{
get; set;
}
}
public sealed class CustomJobContextFactory(IServiceProvider serviceProvider)
: JobContextServiceBase(serviceProvider), IJobContextFactory<CustomJobContext>, ITransient
{
public override CustomJobContext LoadOrCreate(IJob job)
{
var ctx = LoadOrCreateNew(job, Create);
return ctx;
}
protected override CustomJobContext Create() => new();
}
Worker - The Business Logic Execution Unit
- Workers are registered to the DI container using their class name via
KeyedName, or you can specify a string name. If no name is specified, the full class name will be registered in the DI container. - When implementing your own Worker business class, you need to point the WorkerName of the Job to it.
- Each Worker requires a corresponding JobContext to determine the context type.
Simple Implementation
[KeyedName]
public sealed class SimapleWorker(IServiceProvider serviceProvider) : Worker<JobContext>(serviceProvider)
{
protected override async Task ExecuteAsync(CancellationToken cancellationToken)
{
await Task.Delay(500, cancellationToken); //mock execute 0.5 second.
await ExecuteCallbackAsync(true);// work finish callback. It's not necessary.
}
protected override async Task ExecuteCallbackAsync<TResult>(TResult? result) where TResult : default
{
if (result is true)
Logger.LogDebug($"Worker are success.");
await Task.CompletedTask;
}
}
Event - Customizing the Triggering Event for Event-Driven Jobs
- This is the event trigger for the Job's triggering type.
- Each event is different, so it needs to be implemented based on specific business requirements.
- The class name of the event is the name of the class that starts the event detection.
Simple Implementation
[KeyedName]
public sealed class RouterEventer(IServiceProvider serviceProvider) : Eventer<TimeJobContext>(serviceProvider)
{
public override async Task StartAsync(TimeJobContext? jobContext, CancellationToken cancellationToken)
{
await base.StartAsync(jobContext, cancellationToken);
var receiver = new UdpReceiver(ServiceProvider, cancellationToken);
receiver.OnMessage += async (s, e) =>
{
var workerName = jobContext.GetWorkerName();
var worker = ServiceProvider.GetKeyedService<IWorker<TimeJobContext>>(workerName);
if (worker == null)
{
Logger.LogError($"{this} No Worker[{workerName}] was found.");
return;
}
var dnsrst = jobContext!.GetData<DDNSState>() ?? new DDNSState();
jobContext.SetData(dnsrst);
await worker.StartAsync(jobContext, cancellationToken); //trigger the worker.
};
await Task.CompletedTask;
}
}
Watcher - Each Watcher Monitors Certain Types of Jobs
- Each Watcher monitors one or more Jobs.
- Each Watcher corresponds to a specific JobContext.
- It creates the corresponding JobContext for the Job when it starts.
- It is responsible for triggering and managing the Worker of the Job.
- For Jobs of the same Trigger type, there may be multiple Watchers, with Watchers corresponding to Triggers and specific JobContexts.
- You can implement a custom Watcher.
Simple Implementation of an Event Watcher
public sealed class MyEventWatcher(IServiceProvider serviceProvider) : EventWatcherBase<MyJobContext>(serviceProvider)
{
}
Implementation of a Custom Watcher
public sealed class MyWatcher(IServiceProvider serviceProvider) : Watcher<MyJobContext>(serviceProvider), ITransient
{
protected override TriggerStyle TriggerStyle => TriggerStyle.Setup;
protected override async Task ExecuteAsync()
{
var ctxs = JobContexts;
using var logDis = Logger.BeginScopeLog(out string scopedId);
try
{
var runjxs = ctxs.Where(x => !x.Done).OrderBy(x => x.OrderVersion);
foreach (var run in runjxs)
{
if (AppCancellationToken.IsCancellationRequested) break;
var name = run.GetWorkerName();
var worker = ServiceProvider.GetKeyedService<IWorker<SetupJobContext>>(name);
if (worker == null)
Logger.LogError($"{this} No Worker[{name}] was found.");
else
await worker.StartAsync(run, AppCancellationToken);
}
}
catch (Exception ex)
{
Logger.LogError(ex, $"{this} has exception:{ex.Message}");
}
}
}
Usage
Starting from Scratch
Create a New dotnet8 Console Project
- Add the required package references.
NuGet\Install-Package Aicrosoft.Scheduling
NuGet\Install-Package Aicrosoft.Extensions.Hosting
Modify Program.cs
using Aicrosoft.Logging;
using Aicrosoft.Services;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using NLog.Extensions.Logging;
Environment.CurrentDirectory = AppDomain.CurrentDomain.BaseDirectory;
var start = Environment.TickCount64;
var logger = LogHelper.GetCurrentClassLogger();
LogHelper.De(logger);
#if !DEBUG
LogHelper.SetConfigurationVariable("logLevel", "Info");
#endif
try
{
start = Environment.TickCount64;
using IHost host = Host.CreateDefaultBuilder(args)
.AddJobsFactory() //use jobsfactory component.
.AddAsWindowsService("JobAgent")
.AddAppSettingService() //appsettings.json
.AddServices(true,typeof(JobBiz).Assembly) //add entry assembly and biz assebmly to DI.
.AddNLog() //log component
//.AddPluginsService() //enable plugins support.
.AddServiceRegisterDebug() //show DI table.
.Build()
;
ServiceLocator.ServiceProvider = host.Services; //DI Service hook.
await host.RunAsync();
return 0;
}
catch (Exception ex)
{
logger.Error(ex, "Build and run IHost has a exception");
return -9;
}
finally
{
LogHelper.Shutdown();
}
Subsequent Steps
- Add Job configurations or code.
- Implement the business logic Worker.
- If extending JobContext, implement the corresponding JobContextFactory and Worker.
Plugin Mode (Recommended)
- Use the existing open-source JobAgent.
- Create a new library project and add the reference.
NuGet\Install-Package Aicrosoft.Scheduling
NuGet\Install-Package Aicrosoft.Extensions.Hosting
- The plugin assembly is your business module, where you can fully implement complex business logic.
- As long as the corresponding Job description is implemented, it will be called by the framework.
- ⚠️ Add
<EnableDynamicLoading>true</EnableDynamicLoading>to the project properties of the plugin assembly to copy all the package DLLs.
Plugin Configuration Class
- It automatically adds services from the plugin assembly to the DI container.
- It defaults to reading the configuration from
AssemblyName.json.
public class JobAppSetup : PluginSetupBase
{
}
Override the ConfigureServices Method
- If you have custom configuration instances, override the
ConfigureServicesmethod.
public class JobAppSetup : PluginSetupBase
{
protected override void ConfigureServices(HostBuilderContext hostBuilderContext, IServiceCollection services)
{
base.ConfigureServices(hostBuilderContext, services);
services.Configure<DDNSOption>(hostBuilderContext.Configuration.GetSection(DDNSOption.SectionName));
// Add custom service to DI
services.AddSingleton<IMyBiz, MyBiz>();
}
}
Subsequent Steps
- Copy the files generated in the bin directory of the assembly to the
Bin\Plugins\PluginName\directory of JobAgent.
Configuration
Overview
- The
appsettings.jsonin JobAgent is not mandatory. Without it, all plugin modules will be loaded. - Different modules in the
Pluginsdirectory of JobAgent will load their correspondingly named configuration files by default. - If the
Aicrosoft.Extensions.NLogmodule is used,nlog.configis the configuration for NLog.
Job Configuration
🚀 Job configuration is the core of the entire system
You can configure it in the appsettings.json under the root directory of JobAgent, or in the correspondingly named configuration under the plugin directory.
Job Configuration Example
{
"Jobs": {
// SampleJobs is the module name, used to distinguish Jobs with the same Job name in multiple plugin modules.
"SampleJobs": [
{
"enable": false,
"name": "Interval-Woker-Sample1",
"trigger": "00:00:05",
"workerName": "SampleJobs.Aicrosoft.SimpleIntervalWorker, SampleJobs"
},
{
"enable": true,
"name": "L11",
"trigger": "* 0/10 * * * ?",
"priority": "lowest",
"workerName": "LoopSampleWorker"
},
]
}
}
Job Parameter Description
Jobs- A structure keyed by module name, with Job configurations as arrays.name- The name of the Job, not mandatory.enable- Whether to enable this task.trigger- The triggering method, refer to Supported Job Types.workerName- The name in the DI container for the business logic class to be executed after the Job is triggered.timeout- Default is 30 seconds. It is the timeout for executing the specific business logic. Jobs of theStartuptype are not limited by this.extend- A dictionary of typeDictionary<string, string>that provides additional property extensions for this Job.- Subclasses of Job can implement equivalent configurations in the configuration.
Configuration for TimeWatcher
{
"TimeWatcher": {
"Delay": "00:00:30"
},
}
- The node can be omitted and the default value will be used.
- TimeWatcher will run after a 30-second delay by default.
- TimeWatcher monitors Jobs of the
IntervalandSchedulabletypes.
Plugin Configuration
{
"Plugins": {
"PluginsRoot": "Plugins",
"DisableAutoload": true,
"AssemblyNames": {
"SampleJobs": true,
"DDNSJob": false
}
}
}
PluginsRoot- The root directory name for plugins, default isPlugins.DisableAutoload- When set totrue, it will load plugins based on theAssemblyNamesconfiguration instead of automatically loading them.AssemblyNames- Specifies which plugins to load.- If
false, it will automatically load all plugins under thePluginsRootdirectory.
- If
Common Parameter Configuration
{
"Resources": [
{
"name": "NetFile",
"type": "Http",
"value": "http://127.0.0.1/{0:yyyyMMdd}.txt"
},
{
"name": "LocalFile",
"type": "Local",
"value": "Data\\{0:yyyyMMdd}_{1}.txt",
"params": {
"encoding": "gb2312",
"account": "admin",
"password": "admin1234.com"
}
},
{
"name": "RankDb",
"type": "SqlServer",
"value": "Data Source=192.168.1.50;Initial Catalog=DbRank;User ID=sa;Password=sa;App=WST_SG2Rank V1.0"
}
]
}
Obtain Configuration Information
var resources = Services.GetService<IOptions<ResourcesOption>>().Value;
Dynamic Configuration Loading
Dynamic configuration loading is not supported at the moment. If you modify the configuration, you must restart the application.
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 was computed. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
-
net8.0
- Aicrosoft.Extensions (>= 8.0.0-beta.251110.1)
- Aicrosoft.Extensions.Hosting (>= 8.0.0-beta.251110.1)
- Cronos (>= 0.11.1)
- Microsoft.Extensions.Hosting.WindowsServices (>= 8.0.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 8.0.0-beta.251110.1 | 333 | 11/10/2025 |