Merge branch 'dev-v7' into 7.3.0

Conflicts:
	src/Umbraco.Core/Umbraco.Core.csproj
	src/Umbraco.Tests/Integration/GetCultureTests.cs
	src/Umbraco.Tests/Models/ContentTests.cs
	src/Umbraco.Tests/Models/ContentTypeTests.cs
	src/Umbraco.Tests/Persistence/Repositories/ContentTypeRepositoryTest.cs
	src/Umbraco.Tests/Routing/DomainsAndCulturesTests.cs
	src/Umbraco.Tests/Scheduling/BackgroundTaskRunnerTests.cs
	src/Umbraco.Tests/Services/ContentTypeServiceTests.cs
	src/Umbraco.Web/Models/ContentExtensions.cs
	src/Umbraco.Web/Mvc/SurfaceController.cs
This commit is contained in:
Shannon
2015-04-09 16:50:07 +10:00
52 changed files with 988 additions and 439 deletions

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;
}
}
@@ -372,7 +376,8 @@ namespace Umbraco.Web.Scheduling
using (bgTask) // ensure it's disposed
{
if (bgTask.IsAsync)
await bgTask.RunAsync(token);
//configure await = false since we don't care about the context, we're on a background thread.
await bgTask.RunAsync(token).ConfigureAwait(false);
else
bgTask.Run();
}
@@ -388,7 +393,7 @@ namespace Umbraco.Web.Scheduling
catch (Exception ex)
{
LogHelper.Error<BackgroundTaskRunner<T>>("Task has failed.", ex);
}
}
}
#region Events
@@ -402,24 +407,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 +560,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; }
}
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; }
}
}
}