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:
Shannon
2015-04-08 16:28:42 +10:00
parent 3230fce76a
commit 147cc92ac5
5 changed files with 300 additions and 118 deletions

View File

@@ -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);
}

View File

@@ -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.");
}
}
}
}

View 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();
}
}
}

View 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; }
}
}
}

View File

@@ -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" />