Scheduling with BackgroundTaskRunner
In Umbraco 8+ it is possible to run recurring code using the BackgroundTaskRunner
.
Below is a complete example showing how to register a Task Runner with a component that will regularly empty out the recycle bin every five minutes.
Be aware you may or may not want this background task code to run on all servers, if you are using Load Balancing with multiple servers - https://our.umbraco.com/Documentation/Getting-Started/Setup/Server-Setup/Load-Balancing/
using Umbraco.Core;
using Umbraco.Core.Composing;
using Umbraco.Core.Logging;
using Umbraco.Core.Services;
using Umbraco.Web.Scheduling;
namespace Umbraco.Web.UI
{
// We start by setting up a composer and component so our task runner gets registered on application startup
public class CleanUpYourRoomComposer : ComponentComposer<CleanUpYourRoomComponent>
{
}
public class CleanUpYourRoomComponent : IComponent
{
private IProfilingLogger _logger;
private IRuntimeState _runtime;
private IContentService _contentService;
private BackgroundTaskRunner<IBackgroundTask> _cleanUpYourRoomRunner;
public CleanUpYourRoomComponent(IProfilingLogger logger, IRuntimeState runtime, IContentService contentService)
{
_logger = logger;
_runtime = runtime;
_contentService = contentService;
_cleanUpYourRoomRunner = new BackgroundTaskRunner<IBackgroundTask>("CleanYourRoom", _logger);
}
public void Initialize()
{
int delayBeforeWeStart = 60000; // 60000ms = 1min
int howOftenWeRepeat = 300000; //300000ms = 5mins
var task = new CleanRoom(_cleanUpYourRoomRunner, delayBeforeWeStart, howOftenWeRepeat, _runtime, _logger, _contentService);
//As soon as we add our task to the runner it will start to run (after its delay period)
_cleanUpYourRoomRunner.TryAdd(task);
}
public void Terminate()
{
}
}
// Now we get to define the recurring task
public class CleanRoom : RecurringTaskBase
{
private IRuntimeState _runtime;
private IProfilingLogger _logger;
private IContentService _contentService;
public CleanRoom(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayBeforeWeStart, int howOftenWeRepeat, IRuntimeState runtime, IProfilingLogger logger, IContentService contentService)
: base(runner, delayBeforeWeStart, howOftenWeRepeat)
{
_runtime = runtime;
_logger = logger;
_contentService = contentService;
}
public override bool PerformRun()
{
var numberOfThingsInBin = _contentService.CountChildren(Constants.System.RecycleBinContent);
_logger.Info<CleanRoom>("Go clean your room - {ServerRole}", _runtime.ServerRole);
_logger.Info<CleanRoom>("You have {NumberOfThingsInTheBin}", numberOfThingsInBin);
if (_contentService.RecycleBinSmells())
{
// Take out the trash
using (_logger.TraceDuration<CleanRoom>("Mum, I am emptying out the bin", "Its all clean now!"))
{
_contentService.EmptyRecycleBin(userId: -1);
}
}
// If we want to keep repeating - we need to return true
// But if we run into a problem/error & want to stop repeating - return false
return true;
}
public override bool IsAsync => false;
}
}
Trying to inject services or helpers that rely on UmbracoContext
such as UmbracoHelper
or MembershipHelper
will trigger a boot failed error on startup. See the Accessing Published Content outside of a Http Request article to query the Umbraco Published Content using UmbracoContextFactory
.
RecurringTaskBase
This class provides the base class for any recurring task. You can override the PerformRun
method to implement the class. Tasks can also be run asynchronously. In this case, the property IsAsync
must be overridden and set to true
and the PerformRunAsync
must be overridden to implement the class.
BackgroundTaskRunner Events
Background tasks can also trigger events. Browse the API documentation for BackgroundTaskRunner events.
You can subscribe to the events in the Initialize
method of your CleanUpYourRoomComponent
. The events are registered on the BackgroundTaskRunner
object, in this case _cleanUpYourRoomRunner
.
public class CleanUpYourRoomComponent : IComponent
{
private IProfilingLogger _logger;
private IRuntimeState _runtime;
private IContentService _contentService;
private BackgroundTaskRunner<IBackgroundTask> _cleanUpYourRoomRunner;
public CleanUpYourRoomComponent(IProfilingLogger logger, IRuntimeState runtime, IContentService contentService)
{
_logger = logger;
_runtime = runtime;
_contentService = contentService;
_cleanUpYourRoomRunner = new BackgroundTaskRunner<IBackgroundTask>("CleanYourRoom", _logger);
}
public void Initialize()
{
int delayBeforeWeStart = 60000; // 60000ms = 1min
int howOftenWeRepeat = 300000; //300000ms = 5mins
var task = new CleanRoom(_cleanUpYourRoomRunner, delayBeforeWeStart, howOftenWeRepeat, _runtime, _logger, _contentService);
//declare the events
_cleanUpYourRoomRunner.TaskCompleted += Task_Completed;
_cleanUpYourRoomRunner.TaskStarting += this.Task_Starting;
_cleanUpYourRoomRunner.TaskCancelled += this.Task_Cancelled;
_cleanUpYourRoomRunner.TaskError += this.Task_Error;
//As soon as we add our task to the runner it will start to run (after its delay period)
_cleanUpYourRoomRunner.TryAdd(task);
}
private void Task_Error(BackgroundTaskRunner<IBackgroundTask> sender, TaskEventArgs<IBackgroundTask> e)
{
_logger.Info<CleanUpYourRoomComponent>("CleanUpYourRoom error");
}
private void Task_Cancelled(BackgroundTaskRunner<IBackgroundTask> sender, TaskEventArgs<IBackgroundTask> e)
{
_logger.Info<CleanUpYourRoomComponent>("CleanUpYourRoom cancelled");
}
private void Task_Starting(BackgroundTaskRunner<IBackgroundTask> sender, TaskEventArgs<IBackgroundTask> e)
{
_logger.Info<CleanUpYourRoomComponent>("CleanUpYourRoom starting");
}
private void Task_Completed(BackgroundTaskRunner<IBackgroundTask> sender, TaskEventArgs<IBackgroundTask> e)
{
_logger.Info<CleanUpYourRoomComponent>("CleanUpYourRoom run finished");
}
public void Terminate()
{
//unsubscribe during shutdown
_cleanUpYourRoomRunner.TaskCompleted -= Task_Completed;
_cleanUpYourRoomRunner.TaskStarting -= this.Task_Starting;
_cleanUpYourRoomRunner.TaskCancelled -= this.Task_Cancelled;
_cleanUpYourRoomRunner.TaskError -= this.Task_Error;
}
}
Using RuntimeState
In the example above you could add the following switch case at the beginning to help determine the server role & thus if you want to run code on that type of server and exit out early.
// Do not run the code on replicas nor unknown role servers
// ONLY run for Master server or Single
switch (_runtime.ServerRole)
{
case ServerRole.Replica:
_logger.Debug<CleanRoom>("Does not run on replica servers.");
return true; // We return true to try again as the server role may change!
case ServerRole.Unknown:
_logger.Debug<CleanRoom>("Does not run on servers with unknown role.");
return true; // We return true to try again as the server role may change!
}