diff --git a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs index 7cc763e534..3e851a68f0 100644 --- a/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs +++ b/src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs @@ -83,6 +83,30 @@ namespace Umbraco.Tests.Scheduling } } + [Test] + public async void Many_Tasks_Added_Only_Last_Task_Executes_With_Option() + { + var tasks = new Dictionary(); + for (var i = 0; i < 10; i++) + { + tasks.Add(new MyTask(), new ManualResetEvent(false)); + } + + BackgroundTaskRunner tManager; + using (tManager = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions{OnlyProcessLastItem = true})) + { + + tasks.ForEach(t => tManager.Add(t.Key)); + + //wait till the thread is done + await tManager; + + var countExecuted = tasks.Count(x => x.Key.Ended != default(DateTime)); + + Assert.AreEqual(1, countExecuted); + } + } + [Test] public void Tasks_Can_Keep_Being_Added_And_Will_Execute() { diff --git a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs index 60eef43b17..d11ff03d66 100644 --- a/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs +++ b/src/Umbraco.Web/Scheduling/BackgroundTaskRunner.cs @@ -8,6 +8,26 @@ using Umbraco.Core.Logging; namespace Umbraco.Web.Scheduling { + + internal class BackgroundTaskRunnerOptions + { + public BackgroundTaskRunnerOptions() + { + DedicatedThread = false; + PersistentThread = false; + OnlyProcessLastItem = false; + } + + public bool DedicatedThread { get; set; } + public bool PersistentThread { get; set; } + + /// + /// If this is true, the task runner will skip over all items and only process the last/final + /// item registered + /// + public bool OnlyProcessLastItem { get; set; } + } + /// /// This is used to create a background task runner which will stay alive in the background of and complete /// any tasks that are queued. It is web aware and will ensure that it is shutdown correctly when the app domain @@ -17,8 +37,7 @@ namespace Umbraco.Web.Scheduling internal class BackgroundTaskRunner : IDisposable, IRegisteredObject where T : IBackgroundTask { - private readonly bool _dedicatedThread; - private readonly bool _persistentThread; + private readonly BackgroundTaskRunnerOptions _options; private readonly BlockingCollection _tasks = new BlockingCollection(); private Task _consumer; @@ -31,9 +50,15 @@ namespace Umbraco.Web.Scheduling internal event EventHandler> TaskCancelled; public BackgroundTaskRunner(bool dedicatedThread = false, bool persistentThread = false) + : this(new BackgroundTaskRunnerOptions{DedicatedThread = dedicatedThread, PersistentThread = persistentThread}) + { + } + + public BackgroundTaskRunner(BackgroundTaskRunnerOptions options) { - _dedicatedThread = dedicatedThread; - _persistentThread = persistentThread; + if (options == null) throw new ArgumentNullException("options"); + _options = options; + HostingEnvironment.RegisterObject(this); } @@ -146,6 +171,13 @@ namespace Umbraco.Web.Scheduling T remainingTask; while (_tasks.TryTake(out remainingTask)) { + //skip if this is not the last + if (_options.OnlyProcessLastItem && _tasks.Count > 0) + { + //NOTE: don't raise canceled event, we're shutting down + continue; + } + ConsumeTaskInternalAsync(remainingTask) .Wait(); //block until it completes } @@ -181,13 +213,13 @@ namespace Umbraco.Web.Scheduling _consumer = Task.Factory.StartNew(() => StartThreadAsync(token), token, - _dedicatedThread ? TaskCreationOptions.LongRunning : TaskCreationOptions.None, + _options.DedicatedThread ? TaskCreationOptions.LongRunning : TaskCreationOptions.None, TaskScheduler.Default); //if this is not a persistent thread, wait till it's done and shut ourselves down // thus ending the thread or giving back to the thread pool. If another task is added // another thread will spawn or be taken from the pool to process. - if (!_persistentThread) + if (!_options.PersistentThread) { _consumer.ContinueWith(task => ShutDown()); } @@ -228,7 +260,7 @@ namespace Umbraco.Web.Scheduling //When this is false, the thread will process what is currently in the queue and once that is // done, the thread will end and we will shutdown the process - if (_persistentThread) + if (_options.PersistentThread) { //This will iterate over the collection, if there is nothing to take // the thread will block until there is something available. @@ -236,6 +268,13 @@ namespace Umbraco.Web.Scheduling // cancel when we shutdown foreach (var t in _tasks.GetConsumingEnumerable(token)) { + //skip if this is not the last + if (_options.OnlyProcessLastItem && _tasks.Count > 0) + { + OnTaskCancelled(new TaskEventArgs(t)); + continue; + } + await ConsumeTaskCancellableAsync(t, token); } @@ -244,10 +283,17 @@ namespace Umbraco.Web.Scheduling } else { - T repositoryTask; - while (_tasks.TryTake(out repositoryTask)) + T t; + while (_tasks.TryTake(out t)) { - await ConsumeTaskCancellableAsync(repositoryTask, token); + //skip if this is not the last + if (_options.OnlyProcessLastItem && _tasks.Count > 0) + { + OnTaskCancelled(new TaskEventArgs(t)); + continue; + } + + await ConsumeTaskCancellableAsync(t, token); } //the task will end here diff --git a/src/Umbraco.Web/umbraco.presentation/content.cs b/src/Umbraco.Web/umbraco.presentation/content.cs index 2905abaf99..cd736b98ff 100644 --- a/src/Umbraco.Web/umbraco.presentation/content.cs +++ b/src/Umbraco.Web/umbraco.presentation/content.cs @@ -32,7 +32,8 @@ namespace umbraco /// public class content { - private static readonly BackgroundTaskRunner FilePersister = new BackgroundTaskRunner(dedicatedThread: true); + private static readonly BackgroundTaskRunner FilePersister + = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions {DedicatedThread = true, OnlyProcessLastItem = true}); #region Declarations