Open Closed

Periodic-Background-Worker--Different-loop-times-for-different-tenants #9372


User avatar
0
smansuri created

We are implementing a periodic background worker to send daily tasks reminders to users via email. Here, in the worker class we are looping the tasks every 1 hour, which is mentioned inside the constructor of the worker class. Right now since we have mentioned Timer.Period = 3600000; inside the constructor, the worker will be executed every 1 hour for all the tenants. But what we want is that we should be able to set different looping time for different tenants, the value for which would be fetched from the database table.

Currently we have

public class TasksReminderAlertNotificationWorker : AsyncPeriodicBackgroundWorkerBase
{

    public TasksReminderAlertNotificationWorker(
        AbpAsyncTimer timer,
        IServiceScopeFactory serviceScopeFactory)
        : base(timer, serviceScopeFactory)
    {
        Timer.Period = 3600000;
    }
}

Here, as the value of Timer.Period is set as 3600000 milliseconds, the worker will be executed every 1 hour. What we want to achieve is that different tenants should be able to have different value for this Time.Period value (2 hours, 3 hours etc.)

How can we achieve this?

Also, since this worker will be executed every specific time period, it will unnecessarily be executed every hour because we want to run only 1 time in a day and we want to do it at a specific time of the day (for example 10 AM or 5 PM).

Is there a way to achieve that? Instead of looping the worker for the whole day and check for the current time and then based on the condition it will be executed, it should be executed at the exact mentioned time. Please elaborate.

Here's the current piece of code we have implemented.

public class TasksReminderAlertNotificationWorker : AsyncPeriodicBackgroundWorkerBase
{
    private TimeSpan TargetTime;
    protected IConfiguration Configuration;

    public TasksReminderAlertNotificationWorker(
        AbpAsyncTimer timer,
        IServiceScopeFactory serviceScopeFactory,
        IConfiguration configuration)
        : base(timer, serviceScopeFactory)
    {
        Configuration = configuration;
        Timer.Period = Configuration
                      .GetSection("TasksReminderAlertNotificationWorker")
                      .GetValue<int>("Default");
    }

    protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
    {
        var now = DateTime.UtcNow;

        var sharedRepository = workerContext.ServiceProvider
            .GetRequiredService<ISharedRepository>();

        var items = await sharedRepository.GetOrgTasksALertNotificationTeemplate();

        foreach (var item in items)
        {
            double time = 0;

            if (item.ExecutionTime.HasValue)
            {
                time = item.ExecutionTime.Value;
            }

            TimeSpan localTimeOfDay = TimeSpan.FromMilliseconds(time);
            DateTime localDateTime = DateTime.Today.Add(localTimeOfDay);
            DateTime utcDateTime = TimeZoneInfo.ConvertTimeToUtc(localDateTime);
            TargetTime = utcDateTime.TimeOfDay;

            if (now.TimeOfDay >= TargetTime && (item.LastRunOn == null || item.LastRunOn.Value.Date != now.Date))
            {
                var workersAppService = workerContext.ServiceProvider
                    .GetRequiredService<IWorkersAppService>();

                if (item.TenantId.HasValue)
                {
                    await workersAppService.SendTasksNotification(item.TenantId.Value, TargetTime);
                    await sharedRepository.UpdateLastRunOnDateTime(item.Id, now);
                }
            }
        }

        await Task.CompletedTask;
    }
}

1 Answer(s)
  • User Avatar
    0
    EngincanV created
    Support Team .NET Developer

    Hi, thanks for the detailed information and all the code snippets. You're correct that AsyncPeriodicBackgroundWorkerBase executes the job globally at fixed intervals across all tenants, and that it doesn't directly support tenant-specific execution intervals out of the box.

    If you want to apply tenant-specific execution time, you can:

    1. Keep the worker running periodically (every 30-60 minutes globally, for instance)
    2. Inside the DoWorkAsync method, determine whether it's the correct time to run for each tenant, based on values stored in your database. (I guess you are already doing it with ExecutionTime property).

    Optionally, you can optimize the interval (e.g., 15–30 minutes) to reduce delay while still not being too frequent.


    For the second question, it seems you are on the right track and storing a LastRunOn value per tenant to ensure tasks only runs once a day. Maybe you can improve it;

    • Keep the global interval (30 - 60 minutes, for instance)
    • For each tenant, compare the current time with the target time (ExecutionTime) stored in your database.
    • And only execute if these conditions are met.

    Since, setting Timer.Period is a global setting and affects the entire background worker, you need to have a check mechanism to determine when to run for per tenant.

    Regards.

Boost Your Development
ABP Live Training
Packages
See Trainings
Mastering ABP Framework Book
The Official Guide
Mastering
ABP Framework
Learn More
Mastering ABP Framework Book
Made with ❤️ on ABP v9.3.0-preview. Updated on June 13, 2025, 11:37