Updates BackgroundTaskRunner & tests: removes the need for most Thread.Sleep which is error prone, this is done by updating the GetAwaiter() method to be a custom awaiter so we can await the entire background task running operations. We then add a property called CurrentThraedingTask which then allows awaiting the current Task instance. Adds error handling to all event handlers for the task runner. Changes events to be TypedEventHandlers
This commit is contained in:
@@ -6,6 +6,7 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using NUnit.Framework;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Tests.TestHelpers;
|
||||
using Umbraco.Web.Scheduling;
|
||||
|
||||
namespace Umbraco.Tests.Scheduling
|
||||
@@ -13,20 +14,14 @@ namespace Umbraco.Tests.Scheduling
|
||||
[TestFixture]
|
||||
public class BackgroundTaskRunnerTests
|
||||
{
|
||||
private static void AssertRunnerStopsRunning<T>(BackgroundTaskRunner<T> runner, int timeoutMilliseconds = 2000)
|
||||
where T : class, IBackgroundTask
|
||||
[TestFixtureSetUp]
|
||||
public void InitializeFixture()
|
||||
{
|
||||
const int period = 200;
|
||||
|
||||
var i = 0;
|
||||
var m = timeoutMilliseconds/period;
|
||||
while (runner.IsRunning && i++ < m)
|
||||
Thread.Sleep(period);
|
||||
Assert.IsFalse(runner.IsRunning, "Runner is still running.");
|
||||
TestHelper.SetupLog4NetForTests();
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShutdownWaitWhenRunning()
|
||||
public async void ShutdownWaitWhenRunning()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions { AutoStart = true, KeepAlive = true }))
|
||||
{
|
||||
@@ -34,36 +29,41 @@ namespace Umbraco.Tests.Scheduling
|
||||
Thread.Sleep(800); // for long
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
runner.Shutdown(false, true); // -force +wait
|
||||
AssertRunnerStopsRunning(runner);
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
Assert.IsTrue(runner.IsCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShutdownWhenRunning()
|
||||
public async void ShutdownWhenRunning()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
// do NOT try to do this because the code must run on the UI thread which
|
||||
// is not availably, and so the thread never actually starts - wondering
|
||||
// what it means for ASP.NET?
|
||||
//runner.TaskStarting += (sender, args) => Console.WriteLine("starting {0:c}", DateTime.Now);
|
||||
//runner.TaskCompleted += (sender, args) => Console.WriteLine("completed {0:c}", DateTime.Now);
|
||||
Console.WriteLine("Begin {0}", DateTime.Now);
|
||||
|
||||
runner.TaskStarting += (sender, args) => Console.WriteLine("starting {0}", DateTime.Now);
|
||||
runner.TaskCompleted += (sender, args) => Console.WriteLine("completed {0}", DateTime.Now);
|
||||
|
||||
Assert.IsFalse(runner.IsRunning);
|
||||
|
||||
Console.WriteLine("Adding task {0}", DateTime.Now);
|
||||
runner.Add(new MyTask(5000));
|
||||
Assert.IsTrue(runner.IsRunning); // is running the task
|
||||
Console.WriteLine("Shutting down {0}", DateTime.Now);
|
||||
runner.Shutdown(false, false); // -force -wait
|
||||
Assert.IsTrue(runner.IsCompleted);
|
||||
Assert.IsTrue(runner.IsRunning); // still running that task
|
||||
Thread.Sleep(3000);
|
||||
Thread.Sleep(3000); // wait slightly less than the task takes to complete
|
||||
Assert.IsTrue(runner.IsRunning); // still running that task
|
||||
AssertRunnerStopsRunning(runner, 10000);
|
||||
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
|
||||
Console.WriteLine("End {0}", DateTime.Now);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShutdownFlushesTheQueue()
|
||||
public async void ShutdownFlushesTheQueue()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
@@ -74,13 +74,13 @@ namespace Umbraco.Tests.Scheduling
|
||||
runner.Add(t);
|
||||
Assert.IsTrue(runner.IsRunning); // is running the first task
|
||||
runner.Shutdown(false, false); // -force -wait
|
||||
AssertRunnerStopsRunning(runner, 10000);
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
Assert.AreNotEqual(DateTime.MinValue, t.Ended); // t has run
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShutdownForceTruncatesTheQueue()
|
||||
public async void ShutdownForceTruncatesTheQueue()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
@@ -91,13 +91,13 @@ namespace Umbraco.Tests.Scheduling
|
||||
runner.Add(t);
|
||||
Assert.IsTrue(runner.IsRunning); // is running the first task
|
||||
runner.Shutdown(true, false); // +force -wait
|
||||
AssertRunnerStopsRunning(runner, 10000);
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
Assert.AreEqual(DateTime.MinValue, t.Ended); // t has not run
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void ShutdownThenForce()
|
||||
public async void ShutdownThenForce()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
@@ -112,7 +112,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
Thread.Sleep(3000);
|
||||
Assert.IsTrue(runner.IsRunning); // still running that task
|
||||
runner.Shutdown(true, false); // +force -wait
|
||||
AssertRunnerStopsRunning(runner, 20000);
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -126,12 +126,12 @@ namespace Umbraco.Tests.Scheduling
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Create_AutoStart_IsRunning()
|
||||
public async void Create_AutoStart_IsRunning()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions { AutoStart = true }))
|
||||
{
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
AssertRunnerStopsRunning(runner); // though not for long
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -148,7 +148,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Dispose_IsRunning()
|
||||
public async void Dispose_IsRunning()
|
||||
{
|
||||
BackgroundTaskRunner<IBackgroundTask> runner;
|
||||
using (runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions { AutoStart = true, KeepAlive = true }))
|
||||
@@ -157,19 +157,19 @@ namespace Umbraco.Tests.Scheduling
|
||||
// dispose will stop it
|
||||
}
|
||||
|
||||
AssertRunnerStopsRunning(runner);
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
Assert.Throws<InvalidOperationException>(() => runner.Add(new MyTask()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Startup_IsRunning()
|
||||
public async void Startup_IsRunning()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
Assert.IsFalse(runner.IsRunning);
|
||||
runner.StartUp();
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
AssertRunnerStopsRunning(runner); // though not for long
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -186,13 +186,19 @@ namespace Umbraco.Tests.Scheduling
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void Create_AddTask_IsRunning()
|
||||
public async void Create_AddTask_IsRunning()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<BaseTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
var waitHandle = new ManualResetEvent(false);
|
||||
runner.TaskCompleted += (sender, args) =>
|
||||
{
|
||||
waitHandle.Set();
|
||||
};
|
||||
runner.Add(new MyTask());
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
Thread.Sleep(800); // task takes 500ms
|
||||
waitHandle.WaitOne();
|
||||
await runner; //since we are not being kept alive, it will quit
|
||||
Assert.IsFalse(runner.IsRunning);
|
||||
}
|
||||
}
|
||||
@@ -202,11 +208,16 @@ namespace Umbraco.Tests.Scheduling
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<BaseTask>(new BackgroundTaskRunnerOptions { KeepAlive = true }))
|
||||
{
|
||||
var waitHandle = new ManualResetEvent(false);
|
||||
runner.TaskCompleted += (sender, args) =>
|
||||
{
|
||||
Assert.IsTrue(sender.IsRunning);
|
||||
waitHandle.Set();
|
||||
};
|
||||
runner.Add(new MyTask());
|
||||
waitHandle.WaitOne();
|
||||
Thread.Sleep(1000); // we are waiting a second just to prove that it's still running and hasn't been shut off
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
Thread.Sleep(800); // task takes 500ms
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
// dispose will stop it
|
||||
}
|
||||
}
|
||||
|
||||
@@ -218,9 +229,9 @@ namespace Umbraco.Tests.Scheduling
|
||||
var task = new MyTask();
|
||||
Assert.IsTrue(task.Ended == default(DateTime));
|
||||
runner.Add(task);
|
||||
await runner; // wait 'til it's not running anymore
|
||||
await runner.CurrentThreadingTask; // wait for the Task operation to complete
|
||||
Assert.IsTrue(task.Ended != default(DateTime)); // task is done
|
||||
AssertRunnerStopsRunning(runner); // though not for long
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -235,19 +246,19 @@ namespace Umbraco.Tests.Scheduling
|
||||
{
|
||||
tasks.ForEach(runner.Add);
|
||||
|
||||
await runner; // wait 'til it's not running anymore
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
|
||||
// check that tasks are done
|
||||
Assert.IsTrue(tasks.All(x => x.Ended != default(DateTime)));
|
||||
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, runner.TaskStatus);
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, runner.CurrentThreadingTask.Status);
|
||||
Assert.IsFalse(runner.IsRunning);
|
||||
Assert.IsFalse(runner.IsDisposed);
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WaitOnTask()
|
||||
public async void WaitOnTask()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<BaseTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
@@ -258,12 +269,12 @@ namespace Umbraco.Tests.Scheduling
|
||||
runner.Add(task);
|
||||
waitHandle.WaitOne(); // wait 'til task is done
|
||||
Assert.IsTrue(task.Ended != default(DateTime)); // task is done
|
||||
AssertRunnerStopsRunning(runner); // though not for long
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void WaitOnTasks()
|
||||
public async void WaitOnTasks()
|
||||
{
|
||||
var tasks = new Dictionary<BaseTask, ManualResetEvent>();
|
||||
for (var i = 0; i < 10; i++)
|
||||
@@ -278,7 +289,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray());
|
||||
Assert.IsTrue(tasks.All(x => x.Key.Ended != default(DateTime)));
|
||||
|
||||
AssertRunnerStopsRunning(runner); // though not for long
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
@@ -349,9 +360,9 @@ namespace Umbraco.Tests.Scheduling
|
||||
tasks.ForEach(tManager.Add);
|
||||
|
||||
//wait till the thread is done
|
||||
await tManager;
|
||||
await tManager.CurrentThreadingTask;
|
||||
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, tManager.TaskStatus);
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, tManager.CurrentThreadingTask.Status);
|
||||
Assert.IsFalse(tManager.IsRunning);
|
||||
Assert.IsFalse(tManager.IsDisposed);
|
||||
|
||||
@@ -367,14 +378,14 @@ namespace Umbraco.Tests.Scheduling
|
||||
tasks.ForEach(tManager.Add);
|
||||
|
||||
//wait till the thread is done
|
||||
await tManager;
|
||||
await tManager.CurrentThreadingTask;
|
||||
|
||||
foreach (var task in tasks)
|
||||
{
|
||||
Assert.IsTrue(task.Ended != default(DateTime));
|
||||
}
|
||||
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, tManager.TaskStatus);
|
||||
Assert.AreEqual(TaskStatus.RanToCompletion, tManager.CurrentThreadingTask.Status);
|
||||
Assert.IsFalse(tManager.IsRunning);
|
||||
Assert.IsFalse(tManager.IsDisposed);
|
||||
}
|
||||
@@ -383,31 +394,39 @@ namespace Umbraco.Tests.Scheduling
|
||||
[Test]
|
||||
public void RecurringTaskTest()
|
||||
{
|
||||
// note: can have BackgroundTaskRunner<IBackgroundTask> and use it in MyRecurringTask ctor
|
||||
// because that ctor wants IBackgroundTaskRunner<MyRecurringTask> and the generic type
|
||||
// parameter is contravariant ie defined as IBackgroundTaskRunner<in T> so doing the
|
||||
// following is legal:
|
||||
// var IBackgroundTaskRunner<Base> b = ...;
|
||||
// var IBackgroundTaskRunner<Derived> d = b; // legal
|
||||
|
||||
var runCount = 0;
|
||||
var waitHandle = new ManualResetEvent(false);
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
runner.TaskCompleted += (sender, args) => runCount++;
|
||||
runner.TaskStarting += async (sender, args) =>
|
||||
{
|
||||
//wait for each task to finish once it's started
|
||||
await sender.CurrentThreadingTask;
|
||||
if (runCount > 3)
|
||||
{
|
||||
waitHandle.Set();
|
||||
}
|
||||
};
|
||||
|
||||
var task = new MyRecurringTask(runner, 200, 500);
|
||||
MyRecurringTask.RunCount = 0;
|
||||
|
||||
runner.Add(task);
|
||||
Thread.Sleep(5000);
|
||||
Assert.GreaterOrEqual(MyRecurringTask.RunCount, 2); // keeps running, count >= 2
|
||||
|
||||
Assert.IsTrue(runner.IsRunning); // waiting on delay
|
||||
Assert.AreEqual(0, runCount);
|
||||
|
||||
waitHandle.WaitOne();
|
||||
|
||||
Assert.AreEqual(4, runCount);
|
||||
|
||||
// stops recurring
|
||||
runner.Shutdown(false, false);
|
||||
AssertRunnerStopsRunning(runner);
|
||||
|
||||
// timer may try to add a task but it won't work because runner is completed
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DelayedTaskRuns()
|
||||
public async void DelayedTaskRuns()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
@@ -418,14 +437,14 @@ namespace Umbraco.Tests.Scheduling
|
||||
Assert.IsTrue(runner.IsRunning); // still waiting for the task to release
|
||||
Assert.IsFalse(task.HasRun);
|
||||
task.Release();
|
||||
Thread.Sleep(500);
|
||||
await runner.CurrentThreadingTask; //wait for current task to complete
|
||||
Assert.IsTrue(task.HasRun);
|
||||
AssertRunnerStopsRunning(runner); // runs task & exit
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void DelayedTaskStops()
|
||||
public async void DelayedTaskStops()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
@@ -436,7 +455,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
Assert.IsTrue(runner.IsRunning); // still waiting for the task to release
|
||||
Assert.IsFalse(task.HasRun);
|
||||
runner.Shutdown(false, false);
|
||||
AssertRunnerStopsRunning(runner); // runs task & exit
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
Assert.IsTrue(task.HasRun);
|
||||
}
|
||||
}
|
||||
@@ -444,29 +463,36 @@ namespace Umbraco.Tests.Scheduling
|
||||
[Test]
|
||||
public void DelayedRecurring()
|
||||
{
|
||||
var runCount = 0;
|
||||
var waitHandle = new ManualResetEvent(false);
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
runner.TaskCompleted += (sender, args) => runCount++;
|
||||
runner.TaskStarting += async (sender, args) =>
|
||||
{
|
||||
//wait for each task to finish once it's started
|
||||
await sender.CurrentThreadingTask;
|
||||
if (runCount > 3)
|
||||
{
|
||||
waitHandle.Set();
|
||||
}
|
||||
};
|
||||
|
||||
var task = new MyDelayedRecurringTask(runner, 2000, 1000);
|
||||
MyDelayedRecurringTask.RunCount = 0;
|
||||
runner.Add(task);
|
||||
Thread.Sleep(1000);
|
||||
Assert.IsTrue(runner.IsRunning); // waiting on delay
|
||||
Assert.AreEqual(0, MyDelayedRecurringTask.RunCount);
|
||||
Thread.Sleep(1000);
|
||||
Assert.AreEqual(1, MyDelayedRecurringTask.RunCount);
|
||||
Thread.Sleep(5000);
|
||||
Assert.GreaterOrEqual(MyDelayedRecurringTask.RunCount, 2); // keeps running, count >= 2
|
||||
Assert.AreEqual(0, runCount);
|
||||
|
||||
waitHandle.WaitOne();
|
||||
Assert.AreEqual(4, runCount);
|
||||
|
||||
// stops recurring
|
||||
runner.Shutdown(false, false);
|
||||
AssertRunnerStopsRunning(runner);
|
||||
|
||||
// timer may try to add a task but it won't work because runner is completed
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FailingTaskSync()
|
||||
public async void FailingTaskSync()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
@@ -476,14 +502,14 @@ namespace Umbraco.Tests.Scheduling
|
||||
var task = new MyFailingTask(false); // -async
|
||||
runner.Add(task);
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
AssertRunnerStopsRunning(runner); // runs task & exit
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
|
||||
Assert.AreEqual(1, exceptions.Count); // traced and reported
|
||||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void FailingTaskAsync()
|
||||
public async void FailingTaskAsync()
|
||||
{
|
||||
using (var runner = new BackgroundTaskRunner<IBackgroundTask>(new BackgroundTaskRunnerOptions()))
|
||||
{
|
||||
@@ -493,7 +519,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
var task = new MyFailingTask(true); // +async
|
||||
runner.Add(task);
|
||||
Assert.IsTrue(runner.IsRunning);
|
||||
AssertRunnerStopsRunning(runner); // runs task & exit
|
||||
await runner; // wait for the entire runner operation to complete
|
||||
|
||||
Assert.AreEqual(1, exceptions.Count); // traced and reported
|
||||
}
|
||||
@@ -540,8 +566,6 @@ namespace Umbraco.Tests.Scheduling
|
||||
: base(source)
|
||||
{ }
|
||||
|
||||
public static int RunCount { get; set; }
|
||||
|
||||
public override bool IsAsync
|
||||
{
|
||||
get { return false; }
|
||||
@@ -550,7 +574,6 @@ namespace Umbraco.Tests.Scheduling
|
||||
public override void PerformRun()
|
||||
{
|
||||
// nothing to do at the moment
|
||||
RunCount += 1;
|
||||
}
|
||||
|
||||
public override Task PerformRunAsync()
|
||||
@@ -621,7 +644,6 @@ namespace Umbraco.Tests.Scheduling
|
||||
{
|
||||
private readonly int _runMilliseconds;
|
||||
|
||||
public static int RunCount { get; set; }
|
||||
|
||||
public MyRecurringTask(IBackgroundTaskRunner<MyRecurringTask> runner, int runMilliseconds, int periodMilliseconds)
|
||||
: base(runner, periodMilliseconds)
|
||||
@@ -636,8 +658,7 @@ namespace Umbraco.Tests.Scheduling
|
||||
}
|
||||
|
||||
public override void PerformRun()
|
||||
{
|
||||
RunCount += 1;
|
||||
{
|
||||
Thread.Sleep(_runMilliseconds);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Globalization;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web.Hosting;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Events;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
@@ -21,6 +23,7 @@ namespace Umbraco.Web.Scheduling
|
||||
private readonly BlockingCollection<T> _tasks = new BlockingCollection<T>();
|
||||
private readonly object _locker = new object();
|
||||
private readonly ManualResetEventSlim _completedEvent = new ManualResetEventSlim(false);
|
||||
private BackgroundTaskRunnerAwaiter<T> _awaiter;
|
||||
|
||||
private volatile bool _isRunning; // is running
|
||||
private volatile bool _isCompleted; // does not accept tasks anymore, may still be running
|
||||
@@ -28,10 +31,11 @@ namespace Umbraco.Web.Scheduling
|
||||
|
||||
private CancellationTokenSource _tokenSource;
|
||||
|
||||
internal event EventHandler<TaskEventArgs<T>> TaskError;
|
||||
internal event EventHandler<TaskEventArgs<T>> TaskStarting;
|
||||
internal event EventHandler<TaskEventArgs<T>> TaskCompleted;
|
||||
internal event EventHandler<TaskEventArgs<T>> TaskCancelled;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskError;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskStarting;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskCompleted;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskCancelled;
|
||||
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Completed;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class.
|
||||
@@ -48,7 +52,7 @@ namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
if (options == null) throw new ArgumentNullException("options");
|
||||
_options = options;
|
||||
|
||||
|
||||
HostingEnvironment.RegisterObject(this);
|
||||
|
||||
if (options.AutoStart)
|
||||
@@ -80,42 +84,40 @@ namespace Umbraco.Web.Scheduling
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the running task.
|
||||
/// Gets an awaiter used to await the running Threading.Task.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">There is no running task.</exception>
|
||||
/// <remarks>Unless the AutoStart option is true, there will be no running task until
|
||||
/// <remarks>
|
||||
/// Unless the AutoStart option is true, there will be no running task until
|
||||
/// a background task is added to the queue. Unless the KeepAlive option is true, there
|
||||
/// will be no running task when the queue is empty.</remarks>
|
||||
public TaskStatus TaskStatus
|
||||
/// will be no running task when the queue is empty.
|
||||
/// </remarks>
|
||||
public ThreadingTaskAwaiter CurrentThreadingTask
|
||||
{
|
||||
get
|
||||
{
|
||||
if (_runningTask == null)
|
||||
throw new InvalidOperationException("There is no current task.");
|
||||
return _runningTask.Status;
|
||||
throw new InvalidOperationException("There is no current Threading.Task.");
|
||||
return new ThreadingTaskAwaiter(_runningTask);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// Gets an awaiter used to await the running task.
|
||||
/// Gets an awaiter used to await the BackgroundTaskRunner running operation
|
||||
/// </summary>
|
||||
/// <returns>An awaiter for the running task.</returns>
|
||||
/// <returns>An awaiter for the BackgroundTaskRunner running operation</returns>
|
||||
/// <remarks>
|
||||
/// This is just the coolest thing ever, check this article out:
|
||||
/// http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx
|
||||
///
|
||||
/// So long as we have a method called GetAwaiter() that returns an instance of INotifyCompletion
|
||||
/// we can await anything! :)
|
||||
/// </remarks>
|
||||
/// <exception cref="InvalidOperationException">There is no running task.</exception>
|
||||
/// <remarks>Unless the AutoStart option is true, there will be no running task until
|
||||
/// a background task is added to the queue. Unless the KeepAlive option is true, there
|
||||
/// will be no running task when the queue is empty.</remarks>
|
||||
public TaskAwaiter GetAwaiter()
|
||||
/// <para>This is used to wait until the background task runner is no longer running (IsRunning == false)
|
||||
/// </para>
|
||||
/// <para> So long as we have a method called GetAwaiter() that returns an instance of INotifyCompletion
|
||||
/// we can await anything. In this case we are awaiting with a custom BackgroundTaskRunnerAwaiter
|
||||
/// which waits for the Completed event to be raised.
|
||||
/// ref: http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public BackgroundTaskRunnerAwaiter<T> GetAwaiter()
|
||||
{
|
||||
if (_runningTask == null)
|
||||
throw new InvalidOperationException("There is no current task.");
|
||||
return _runningTask.GetAwaiter();
|
||||
return _awaiter ?? (_awaiter = new BackgroundTaskRunnerAwaiter<T>(this));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -261,6 +263,8 @@ namespace Umbraco.Web.Scheduling
|
||||
_isRunning = false; // done
|
||||
if (_options.PreserveRunningTask == false)
|
||||
_runningTask = null;
|
||||
//raise event
|
||||
OnCompleted();
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -388,7 +392,7 @@ namespace Umbraco.Web.Scheduling
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("Task has failed.", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Events
|
||||
@@ -402,24 +406,70 @@ namespace Umbraco.Web.Scheduling
|
||||
protected virtual void OnTaskStarting(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskStarting;
|
||||
if (handler != null) handler(this, e);
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("TaskStarting exception occurred", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnTaskCompleted(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskCompleted;
|
||||
if (handler != null) handler(this, e);
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("TaskCompleted exception occurred", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void OnTaskCancelled(TaskEventArgs<T> e)
|
||||
{
|
||||
var handler = TaskCancelled;
|
||||
if (handler != null) handler(this, e);
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, e);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("TaskCancelled exception occurred", ex);
|
||||
}
|
||||
}
|
||||
|
||||
//dispose it
|
||||
e.Task.Dispose();
|
||||
}
|
||||
|
||||
protected virtual void OnCompleted()
|
||||
{
|
||||
var handler = Completed;
|
||||
if (handler != null)
|
||||
{
|
||||
try
|
||||
{
|
||||
handler(this, EventArgs.Empty);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogHelper.Error<BackgroundTaskRunner<T>>("OnCompleted exception occurred", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region IDisposable
|
||||
@@ -509,5 +559,7 @@ namespace Umbraco.Web.Scheduling
|
||||
LogHelper.Info<BackgroundTaskRunner<T>>("Down.");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
65
src/Umbraco.Web/Scheduling/BackgroundTaskRunnerAwaiter.cs
Normal file
65
src/Umbraco.Web/Scheduling/BackgroundTaskRunnerAwaiter.cs
Normal file
@@ -0,0 +1,65 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Custom awaiter used to await when the BackgroundTaskRunner is completed (IsRunning == false)
|
||||
/// </summary>
|
||||
/// <typeparam name="T"></typeparam>
|
||||
/// <remarks>
|
||||
/// This custom awaiter simply uses a TaskCompletionSource to set the result when the Completed event of the
|
||||
/// BackgroundTaskRunner executes.
|
||||
/// A custom awaiter requires implementing INotifyCompletion as well as IsCompleted, OnCompleted and GetResult
|
||||
/// see: http://blogs.msdn.com/b/pfxteam/archive/2011/01/13/10115642.aspx
|
||||
/// </remarks>
|
||||
internal class BackgroundTaskRunnerAwaiter<T> : INotifyCompletion where T : class, IBackgroundTask
|
||||
{
|
||||
private readonly BackgroundTaskRunner<T> _runner;
|
||||
private readonly TaskCompletionSource<int> _tcs;
|
||||
private readonly TaskAwaiter<int> _awaiter;
|
||||
|
||||
public BackgroundTaskRunnerAwaiter(BackgroundTaskRunner<T> runner)
|
||||
{
|
||||
if (runner == null) throw new ArgumentNullException("runner");
|
||||
_runner = runner;
|
||||
|
||||
_tcs = new TaskCompletionSource<int>();
|
||||
|
||||
if (_runner.IsRunning)
|
||||
{
|
||||
_runner.Completed += (s, e) => _tcs.SetResult(0);
|
||||
}
|
||||
else
|
||||
{
|
||||
//not running, just set the result
|
||||
_tcs.SetResult(0);
|
||||
}
|
||||
_awaiter = _tcs.Task.GetAwaiter();
|
||||
}
|
||||
|
||||
public BackgroundTaskRunnerAwaiter<T> GetAwaiter()
|
||||
{
|
||||
return this;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// This is completed when the runner is finished running
|
||||
/// </summary>
|
||||
public bool IsCompleted
|
||||
{
|
||||
get { return _runner.IsRunning == false && _tcs.Task.IsCompleted; }
|
||||
}
|
||||
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
_awaiter.OnCompleted(continuation);
|
||||
}
|
||||
|
||||
public void GetResult()
|
||||
{
|
||||
_awaiter.GetResult();
|
||||
}
|
||||
}
|
||||
}
|
||||
42
src/Umbraco.Web/Scheduling/ThreadingTaskAwaiter.cs
Normal file
42
src/Umbraco.Web/Scheduling/ThreadingTaskAwaiter.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// This is used to return an awaitable instance from a Task without actually returning the
|
||||
/// underlying Task instance since it shouldn't be mutable.
|
||||
/// </summary>
|
||||
internal class ThreadingTaskAwaiter
|
||||
{
|
||||
private readonly Task _task;
|
||||
|
||||
public ThreadingTaskAwaiter(Task task)
|
||||
{
|
||||
if (task == null) throw new ArgumentNullException("task");
|
||||
_task = task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// With a GetAwaiter declared it means that this instance can be awaited on with the await keyword
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
public TaskAwaiter GetAwaiter()
|
||||
{
|
||||
return _task.GetAwaiter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the status of the running task.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">There is no running task.</exception>
|
||||
/// <remarks>Unless the AutoStart option is true, there will be no running task until
|
||||
/// a background task is added to the queue. Unless the KeepAlive option is true, there
|
||||
/// will be no running task when the queue is empty.</remarks>
|
||||
public TaskStatus Status
|
||||
{
|
||||
get { return _task.Status; }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -274,6 +274,7 @@
|
||||
<Compile Include="Mvc\DisableClientCacheAttribute.cs" />
|
||||
<Compile Include="Mvc\MvcVersionCheck.cs" />
|
||||
<Compile Include="Mvc\ReflectedFixedRazorViewEngine.cs" />
|
||||
<Compile Include="Scheduling\ThreadingTaskAwaiter.cs" />
|
||||
<Compile Include="Scheduling\BackgroundTaskRunner.cs" />
|
||||
<Compile Include="BatchedServerMessenger.cs" />
|
||||
<Compile Include="CacheHelperExtensions.cs" />
|
||||
@@ -498,6 +499,7 @@
|
||||
<Compile Include="Mvc\UmbracoVirtualNodeRouteHandler.cs" />
|
||||
<Compile Include="Routing\CustomRouteUrlProvider.cs" />
|
||||
<Compile Include="Routing\UrlProviderExtensions.cs" />
|
||||
<Compile Include="Scheduling\BackgroundTaskRunnerAwaiter.cs" />
|
||||
<Compile Include="Scheduling\BackgroundTaskRunnerOptions.cs" />
|
||||
<Compile Include="Scheduling\DelayedRecurringTaskBase.cs" />
|
||||
<Compile Include="Scheduling\IBackgroundTaskRunner.cs" />
|
||||
|
||||
Reference in New Issue
Block a user