abpframework/abp · error · AbpException

Both 'Period' and 'CronExpression' are not set for {worker.G

Error message

Both 'Period' and 'CronExpression' are not set for {worker.GetType().FullName}. You must set at least one of them.

What it means

Thrown by AbpTickerQBackgroundWorkerManager.AddAsync when registering an AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase that has neither a Period nor a CronExpression set. TickerQ needs at least one to build the scheduling function; without either, it cannot determine when to invoke the worker.

Source

Thrown at framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkerManager.cs:49

        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.AddFunction(name!, async (tickerQCancellationToken, serviceProvider, tickerFunctionContext) =>
            {
                var workerInvoker = new AbpTickerQPeriodicBackgroundWorkerInvoker(worker, serviceProvider);
                await workerInvoker.DoWorkAsync(tickerFunctionContext, tickerQCancellationToken);
            }, config?.Priority ?? TickerTaskPriority.LongRunning, config?.MaxConcurrency ?? 0);

            AbpTickerQBackgroundWorkersProvider.BackgroundWorkers.Add(name!, new AbpTickerQCronBackgroundWorker
            {
                Function = name!,
                CronExpression = cronExpression,
                WorkerType = ProxyHelper.GetUnProxiedType(worker)
            });

View on GitHub (pinned to 7ed43b1931)

Solutions

  1. Set Period in the worker constructor: Period = 60000; (or load it from IOptions/ISettingProvider).
  2. Set CronExpression on the worker if you want cron-based scheduling instead of a fixed period.
  3. If the value should come from configuration, validate that the config key exists and is non-null before assigning.

Example fix

// before — Period never set
public class MyCleanupWorker : AsyncPeriodicBackgroundWorkerBase
{
    public MyCleanupWorker()
    {
        // Period and CronExpression both unset
    }
    protected override Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
    {
        return Task.CompletedTask;
    }
}

// after
public class MyCleanupWorker : AsyncPeriodicBackgroundWorkerBase
{
    public MyCleanupWorker()
    {
        Period = 3600000; // 1 hour
    }
    protected override Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
    {
        return Task.CompletedTask;
    }
}
Defensive patterns

Strategy: validation

Validate before calling

if (worker is AsyncPeriodicBackgroundWorkerBase a && a.Period == null && a.CronExpression.IsNullOrWhiteSpace())
{
    throw new InvalidOperationException($"Worker {worker.GetType().FullName} has no Period and no CronExpression. Set one before registration.");
}
await manager.AddAsync(worker, ct);

Type guard

static bool IsScheduled(AsyncPeriodicBackgroundWorkerBase w) => w.Period != null || !w.CronExpression.IsNullOrWhiteSpace();

Prevention

When it happens

Trigger: Registering a periodic worker with the TickerQ provider where the worker's Period property was never assigned and no CronExpression was set in the constructor. This can also happen if Period is explicitly set to null or left at its default (null) in a custom worker subclass.

Common situations: Subclassing AsyncPeriodicBackgroundWorkerBase and forgetting to set Period in the constructor. Reading the period from configuration that returned null (e.g. appsettings missing the key). Using a base worker type that was intended for a different provider that does not require Period.

Related errors


AI-assisted analysis of abpframework/abp@7ed43b1931 (2026-08-13). Data as JSON: /api/errors/c2ff2f4cc805789b. Report an issue: GitHub.