Change Period not working in IRecurringBackgroundJ...
# help-with-umbraco
c
V13.6.0 Have implemented a recurring background job to delete some files on a regular basis. It works fine. The time period is set by the editors in the back office. However, it only works from app start. I followed the "Complex example" in the docs (https://docs.umbraco.com/umbraco-cms/13.latest/reference/scheduling) but don't see where it actually picks up the change. I've tried adding
Period = TimeSpan.FromMinutes(GetTime());
just before the return, outside the scope, but although I can see it picks up the new time there, the schedule is still running on the old time. Is there some tweak that's not in the docs or have I simply misunderstood how the PeriodChanged thing works? I note there is this section:-
Copy code
private event EventHandler? _periodChanged;
    public event EventHandler PeriodChanged
    {
        add { _periodChanged += value; }
        remove { _periodChanged -= value; }
    }
Not sure what populates the
value
. Should that be my GetTime() function? Any advice appreciated. Thanks.
a
Did you also add the part that updates the periodChange?
Copy code
healthChecksSettings.OnChange(x =>
        {
            Period = x.Notification.Period;
            _periodChanged?.Invoke(this, EventArgs.Empty);
        });
c
Yes, that was always there.
a
Is the value properly changed ?
perhaps let it write to log each x min to test it
c
Well I can see Period change in debug so it should pick it up shouldn't it? But that's a good idea, I'll add it to the log output I already use.
So I added the Period to the logger, let it run for a couple of cycles on 2 mins, changed it to 3 mins and let it run on a few more. As you can see from the output below, even though it picked up the Period change, it still ran on 2 minute updates! [13:27:11 INF] PDF folder checked: was empty. [13:27:11 INF] PDF folder checked: Period: 00:02:00 [13:29:11 INF] PDF folder checked: was empty. [13:29:11 INF] PDF folder checked: Period: 00:02:00 [13:29:52 INF] Document Site Settings (id=1069) has been published. (change to 3 mins here) [13:29:52 INF] Adding examine event handlers for 4 index providers. [13:31:11 INF] PDF folder checked: was empty. [13:31:11 INF] PDF folder checked: Period: 00:02:00 [13:33:11 INF] PDF folder checked: was empty. [13:33:11 INF] PDF folder checked: Period: 00:03:00 [13:35:11 INF] PDF folder checked: was empty. [13:35:11 INF] PDF folder checked: Period: 00:03:00 I use a function to get the time from the back office. But it's reading the cache, should it be using the content service instead? I'm thinking the cache might not be updated till the page is called.
Copy code
private int GetTime() {
        using var context = _contextFactory.EnsureUmbracoContext();
        if (context.UmbracoContext.Content == null) {
            throw new InvalidOperationException("Application can't access nucache...");
        }

        var siteSettings = context.UmbracoContext.Content.GetAtRoot().First().FirstChild<SiteSettings>();
        int scheduledTimePeriod = siteSettings?.PDfclearanceSchedule ?? 3;
           
        return scheduledTimePeriod;
    }
k
I am inheriting
RecurringHostedBase
. and with that you can call the
ChangePeriod
method, and it sets it (and i can confim it works) so my class
Copy code
cs
internal class ProcessingQueueBackgroundHostedService
    : RecurringHostedServiceBase
{
....
has this method. (you can ignore the buffer stuff, just for me that).
Copy code
cs
   private void SetPeriod(TimeSpan period, bool addBuffer)
   {
       _currentPeriod = period + (addBuffer ? TimeSpan.FromSeconds(1) : TimeSpan.Zero);
       _logger.LogDebug("Setting period to {time} seconds", _currentPeriod.TotalSeconds);

       ChangePeriod(_currentPeriod);
   }
but the change has to happen in the processing, you can't change the time outside of the process. because they way it works internally, is it executes the process then sets the timer to fire for next time, if you change the time outside of that loop, it doesn't change the existingf timer to fire anytime sooner.
if you want your background task to run 'now' then you are probibly better queuing the bacground task on the background TaskQueue. e.g
Copy code
cs
_backgroundTaskQueue.QueueBackgroundWorkItem(
    cancellationToken => Task.Run(() =>
    {    _backgroundService.ProcessQueueItem(notification.QueuedItem.Key);
    }, cancellationToken));
if you do this, then you need to put some guard in place for the background recurring task and the thing you have put into a background thread running at the same time. A semaphore is usually the way to go.
Copy code
cs
 if (await _processingSemaphore.WaitAsync(TimeSpan.FromMinutes(_waitTime)))
 {
     try
     {
         // do the things you need here.
     }
     catch (Exception ex)
     {
         _logger.LogError(ex, "Error")
     }
     finally
     {
         _processingSemaphore.Release();
     }
 }
So as you might be able to tell here, i have a
backgroundHostedService
and a
backgroundService
all the work goes on in the backgroundService the backgroundHostedService is calling it from it's own PerformExecuteAsync method, but also because all the work and guards are in the backgroundservice i can put it on a background thread, and be OK that they won't clash.
c
Hi Kevin. Thanks for that. I've read it a few times but still don't quite get it I'm afraid. I don't need things to happen instantly, the Period will be in hours when live. It's just minutes for my sanity while dev'ing. If I add
Copy code
internal class ProcessingQueueBackgroundHostedService
    : RecurringHostedServiceBase
I just get an error saying "Base class 'Umbraco.Cms.Infrastructure.HostedServices.RecurringHostedServiceBase' does not contain parameterless constructor" and I can't find any info on it's proper use. Looks like if I could get that bit in and pick up the new period from the backoffice via my GetTime() function then I might be there.
k
Ok, I have posted my whole backgroundService class here https://gist.github.com/KevinJump/8ff8a5d2f59034fb3c891f17d8f1b4cd i think you will need to create the constructor first so your class can build, but the rest of it is fairly self explanatory (hopefully). *As to what this is doing : we are doing a periodic check of a DB table, for things to be due, when something comes back with a data in the future, we work out if that is going to be before the next time we are due to run, and if it is, we set the period so that we will trigger just at the time the thing is due to happen (so we don't have to wait).
14 Views