{"record":{"id":"c2ff2f4cc805789b","repo":"abpframework/abp","slug":"both-period-and-cronexpression-are-not-set-for","errorCode":null,"errorMessage":"Both 'Period' and 'CronExpression' are not set for {worker.GetType().FullName}. You must set at least one of them.","messagePattern":"Both 'Period' and 'CronExpression' are not set for (.+?)\\. You must set at least one of them\\.","errorType":"exception","errorClass":"AbpException","httpStatus":null,"severity":"error","filePath":"framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkerManager.cs","lineNumber":49,"sourceCode":"        if (worker is AsyncPeriodicBackgroundWorkerBase or PeriodicBackgroundWorkerBase)\n        {\n            int? period = null;\n            string? cronExpression = null;\n\n            if (worker is AsyncPeriodicBackgroundWorkerBase asyncPeriodicBackgroundWorkerBase)\n            {\n                period = asyncPeriodicBackgroundWorkerBase.Period;\n                cronExpression = asyncPeriodicBackgroundWorkerBase.CronExpression;\n            }\n            else if (worker is PeriodicBackgroundWorkerBase periodicBackgroundWorkerBase)\n            {\n                period = periodicBackgroundWorkerBase.Period;\n                cronExpression = periodicBackgroundWorkerBase.CronExpression;\n            }\n\n            if (period == null && cronExpression.IsNullOrWhiteSpace())\n            {\n                throw new AbpException($\"Both 'Period' and 'CronExpression' are not set for {worker.GetType().FullName}. You must set at least one of them.\");\n            }\n\n            cronExpression = cronExpression ?? GetCron(period!.Value);\n            var name = BackgroundWorkerNameAttribute.GetNameOrNull(worker.GetType()) ?? worker.GetType().FullName;\n\n            var config = Options.GetConfigurationOrNull(ProxyHelper.GetUnProxiedType(worker));\n            AbpTickerQFunctionProvider.AddFunction(name!, async (tickerQCancellationToken, serviceProvider, tickerFunctionContext) =>\n            {\n                var workerInvoker = new AbpTickerQPeriodicBackgroundWorkerInvoker(worker, serviceProvider);\n                await workerInvoker.DoWorkAsync(tickerFunctionContext, tickerQCancellationToken);\n            }, config?.Priority ?? TickerTaskPriority.LongRunning, config?.MaxConcurrency ?? 0);\n\n            AbpTickerQBackgroundWorkersProvider.BackgroundWorkers.Add(name!, new AbpTickerQCronBackgroundWorker\n            {\n                Function = name!,\n                CronExpression = cronExpression,\n                WorkerType = ProxyHelper.GetUnProxiedType(worker)\n            });","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/abpframework/abp/blob/7ed43b1931b9df46a50c0c59148a18645641d0df/framework/src/Volo.Abp.BackgroundWorkers.TickerQ/Volo/Abp/BackgroundWorkers/TickerQ/AbpTickerQBackgroundWorkerManager.cs#L31-L67","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set Period in the worker constructor: Period = 60000; (or load it from IOptions/ISettingProvider).","Set CronExpression on the worker if you want cron-based scheduling instead of a fixed period.","If the value should come from configuration, validate that the config key exists and is non-null before assigning."],"exampleFix":"// before — Period never set\npublic class MyCleanupWorker : AsyncPeriodicBackgroundWorkerBase\n{\n    public MyCleanupWorker()\n    {\n        // Period and CronExpression both unset\n    }\n    protected override Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)\n    {\n        return Task.CompletedTask;\n    }\n}\n\n// after\npublic class MyCleanupWorker : AsyncPeriodicBackgroundWorkerBase\n{\n    public MyCleanupWorker()\n    {\n        Period = 3600000; // 1 hour\n    }\n    protected override Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)\n    {\n        return Task.CompletedTask;\n    }\n}","handlingStrategy":"validation","validationCode":"if (worker is AsyncPeriodicBackgroundWorkerBase a && a.Period == null && a.CronExpression.IsNullOrWhiteSpace())\n{\n    throw new InvalidOperationException($\"Worker {worker.GetType().FullName} has no Period and no CronExpression. Set one before registration.\");\n}\nawait manager.AddAsync(worker, ct);","typeGuard":"static bool IsScheduled(AsyncPeriodicBackgroundWorkerBase w) => w.Period != null || !w.CronExpression.IsNullOrWhiteSpace();","tryCatchPattern":null,"preventionTips":["Always set Period or CronExpression in the worker's constructor.","If the period comes from configuration, assert it is non-null/non-zero before assigning.","Add a startup validation pass over all registered periodic workers to confirm scheduling is set."],"tags":["tickerq","background-workers","scheduling","configuration","startup"],"backgroundTag":null,"analyzedSha":"7ed43b1931b9df46a50c0c59148a18645641d0df","analyzedAt":"2026-08-13T16:26:11.351Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}