From b681ca5e1708ad3620e0209023aebb560c897f19 Mon Sep 17 00:00:00 2001 From: Shannon Date: Wed, 4 Feb 2015 14:59:33 +1100 Subject: [PATCH] Updates BackgroundTaskRunner to support more complex options including the ability to only execute the last/final task in the queue. Added tests to support, updated the 'content' object to use this option so that only the last task in the queue will execute so that file persisting doesn't get queued but the correctly queued data will be written. --- .../Scheduling/BackgroundTaskRunnerTests.cs | 24 +++++++ .../Scheduling/BackgroundTaskRunner.cs | 66 ++++++++++++++++--- .../umbraco.presentation/content.cs | 3 +- 3 files changed, 82 insertions(+), 11 deletions(-) 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 63c0652edf..f7500cc493 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