Merge branch 'netcore/dev' into netcore/feature/move-mappings-after-httpcontext
This commit is contained in:
53
src/Umbraco.Core/Scheduling/BackgroundTaskRunnerOptions.cs
Normal file
53
src/Umbraco.Core/Scheduling/BackgroundTaskRunnerOptions.cs
Normal file
@@ -0,0 +1,53 @@
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides options to the <see cref="BackgroundTaskRunner{T}"/> class.
|
||||
/// </summary>
|
||||
public class BackgroundTaskRunnerOptions
|
||||
{
|
||||
// TODO: Could add options for using a stack vs queue if required
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="BackgroundTaskRunnerOptions"/> class.
|
||||
/// </summary>
|
||||
public BackgroundTaskRunnerOptions()
|
||||
{
|
||||
LongRunning = false;
|
||||
KeepAlive = false;
|
||||
AutoStart = false;
|
||||
PreserveRunningTask = false;
|
||||
Hosted = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the running task should be a long-running,
|
||||
/// coarse grained operation.
|
||||
/// </summary>
|
||||
public bool LongRunning { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the running task should block and wait
|
||||
/// on the queue, or end, when the queue is empty.
|
||||
/// </summary>
|
||||
public bool KeepAlive { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the running task should start immediately
|
||||
/// or only once a task has been added to the queue.
|
||||
/// </summary>
|
||||
public bool AutoStart { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the running task should be preserved
|
||||
/// once completed, or reset to null. For unit tests.
|
||||
/// </summary>
|
||||
public bool PreserveRunningTask { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether the runner should register with (and be
|
||||
/// stopped by) the hosting. Otherwise, something else should take care of stopping
|
||||
/// the runner. True by default.
|
||||
/// </summary>
|
||||
public bool Hosted { get; set; }
|
||||
}
|
||||
}
|
||||
30
src/Umbraco.Core/Scheduling/IBackgroundTask.cs
Normal file
30
src/Umbraco.Core/Scheduling/IBackgroundTask.cs
Normal file
@@ -0,0 +1,30 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a background task.
|
||||
/// </summary>
|
||||
public interface IBackgroundTask : IDisposable
|
||||
{
|
||||
/// <summary>
|
||||
/// Runs the background task.
|
||||
/// </summary>
|
||||
void Run();
|
||||
|
||||
/// <summary>
|
||||
/// Runs the task asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="token">A cancellation token.</param>
|
||||
/// <returns>A <see cref="Task"/> instance representing the execution of the background task.</returns>
|
||||
/// <exception cref="NotImplementedException">The background task cannot run asynchronously.</exception>
|
||||
Task RunAsync(CancellationToken token);
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the background task can run asynchronously.
|
||||
/// </summary>
|
||||
bool IsAsync { get; }
|
||||
}
|
||||
}
|
||||
20
src/Umbraco.Core/Scheduling/IBackgroundTaskRunner.cs
Normal file
20
src/Umbraco.Core/Scheduling/IBackgroundTaskRunner.cs
Normal file
@@ -0,0 +1,20 @@
|
||||
using System;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a service managing a queue of tasks of type <typeparamref name="T"/> and running them in the background.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the managed tasks.</typeparam>
|
||||
/// <remarks>The interface is not complete and exists only to have the contravariance on T.</remarks>
|
||||
public interface IBackgroundTaskRunner<in T> : IDisposable, IRegisteredObject
|
||||
where T : class, IBackgroundTask
|
||||
{
|
||||
bool IsCompleted { get; }
|
||||
void Add(T task);
|
||||
bool TryAdd(T task);
|
||||
|
||||
// TODO: complete the interface?
|
||||
}
|
||||
}
|
||||
32
src/Umbraco.Core/Scheduling/ILatchedBackgroundTask.cs
Normal file
32
src/Umbraco.Core/Scheduling/ILatchedBackgroundTask.cs
Normal file
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a latched background task.
|
||||
/// </summary>
|
||||
/// <remarks>Latched background tasks can suspend their execution until
|
||||
/// a condition is met. However if the tasks runner has to terminate,
|
||||
/// latched background tasks can be executed immediately, depending on
|
||||
/// the value returned by RunsOnShutdown.</remarks>
|
||||
public interface ILatchedBackgroundTask : IBackgroundTask
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets a task on latch.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException">The task is not latched.</exception>
|
||||
Task Latch { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the task is latched.
|
||||
/// </summary>
|
||||
/// <remarks>Should return false as soon as the condition is met.</remarks>
|
||||
bool IsLatched { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets a value indicating whether the task can be executed immediately if the task runner has to terminate.
|
||||
/// </summary>
|
||||
bool RunsOnShutdown { get; }
|
||||
}
|
||||
}
|
||||
81
src/Umbraco.Core/Scheduling/KeepAlive.cs
Normal file
81
src/Umbraco.Core/Scheduling/KeepAlive.cs
Normal file
@@ -0,0 +1,81 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Configuration.UmbracoSettings;
|
||||
using Umbraco.Core.Logging;
|
||||
using Umbraco.Core.Sync;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
public class KeepAlive : RecurringTaskBase
|
||||
{
|
||||
private readonly IRuntimeState _runtime;
|
||||
private readonly IKeepAliveSection _keepAliveSection;
|
||||
private readonly IProfilingLogger _logger;
|
||||
private static HttpClient _httpClient;
|
||||
|
||||
public KeepAlive(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
|
||||
IRuntimeState runtime, IKeepAliveSection keepAliveSection, IProfilingLogger logger)
|
||||
: base(runner, delayMilliseconds, periodMilliseconds)
|
||||
{
|
||||
_runtime = runtime;
|
||||
_keepAliveSection = keepAliveSection;
|
||||
_logger = logger;
|
||||
if (_httpClient == null)
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public override async Task<bool> PerformRunAsync(CancellationToken token)
|
||||
{
|
||||
// not on replicas nor unknown role servers
|
||||
switch (_runtime.ServerRole)
|
||||
{
|
||||
case ServerRole.Replica:
|
||||
_logger.Debug<KeepAlive>("Does not run on replica servers.");
|
||||
return true; // role may change!
|
||||
case ServerRole.Unknown:
|
||||
_logger.Debug<KeepAlive>("Does not run on servers with unknown role.");
|
||||
return true; // role may change!
|
||||
}
|
||||
|
||||
// ensure we do not run if not main domain, but do NOT lock it
|
||||
if (_runtime.IsMainDom == false)
|
||||
{
|
||||
_logger.Debug<KeepAlive>("Does not run if not MainDom.");
|
||||
return false; // do NOT repeat, going down
|
||||
}
|
||||
|
||||
using (_logger.DebugDuration<KeepAlive>("Keep alive executing", "Keep alive complete"))
|
||||
{
|
||||
var keepAlivePingUrl = _keepAliveSection.KeepAlivePingUrl;
|
||||
try
|
||||
{
|
||||
if (keepAlivePingUrl.Contains("{umbracoApplicationUrl}"))
|
||||
{
|
||||
var umbracoAppUrl = _runtime.ApplicationUrl.ToString();
|
||||
if (umbracoAppUrl.IsNullOrWhiteSpace())
|
||||
{
|
||||
_logger.Warn<KeepAlive>("No umbracoApplicationUrl for service (yet), skip.");
|
||||
return true; // repeat
|
||||
}
|
||||
|
||||
keepAlivePingUrl = keepAlivePingUrl.Replace("{umbracoApplicationUrl}", umbracoAppUrl.TrimEnd('/'));
|
||||
}
|
||||
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, keepAlivePingUrl);
|
||||
var result = await _httpClient.SendAsync(request, token);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error<KeepAlive>(ex, "Keep alive failed (at '{keepAlivePingUrl}').", keepAlivePingUrl);
|
||||
}
|
||||
}
|
||||
|
||||
return true; // repeat
|
||||
}
|
||||
|
||||
public override bool IsAsync => true;
|
||||
}
|
||||
}
|
||||
61
src/Umbraco.Core/Scheduling/LatchedBackgroundTaskBase.cs
Normal file
61
src/Umbraco.Core/Scheduling/LatchedBackgroundTaskBase.cs
Normal file
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Umbraco.Core;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
public abstract class LatchedBackgroundTaskBase : DisposableObjectSlim, ILatchedBackgroundTask
|
||||
{
|
||||
private TaskCompletionSource<bool> _latch;
|
||||
|
||||
protected LatchedBackgroundTaskBase()
|
||||
{
|
||||
_latch = new TaskCompletionSource<bool>();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements IBackgroundTask.Run().
|
||||
/// </summary>
|
||||
public virtual void Run()
|
||||
{
|
||||
throw new NotSupportedException("This task cannot run synchronously.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements IBackgroundTask.RunAsync().
|
||||
/// </summary>
|
||||
public virtual Task RunAsync(CancellationToken token)
|
||||
{
|
||||
throw new NotSupportedException("This task cannot run asynchronously.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Indicates whether the background task can run asynchronously.
|
||||
/// </summary>
|
||||
public abstract bool IsAsync { get; }
|
||||
|
||||
public Task Latch => _latch.Task;
|
||||
|
||||
public bool IsLatched => _latch.Task.IsCompleted == false;
|
||||
|
||||
protected void Release()
|
||||
{
|
||||
_latch.SetResult(true);
|
||||
}
|
||||
|
||||
protected void Reset()
|
||||
{
|
||||
_latch = new TaskCompletionSource<bool>();
|
||||
}
|
||||
|
||||
public virtual bool RunsOnShutdown => false;
|
||||
|
||||
// the task is going to be disposed after execution,
|
||||
// unless it is latched again, thus indicating it wants to
|
||||
// remain active
|
||||
|
||||
protected override void DisposeResources()
|
||||
{ }
|
||||
}
|
||||
}
|
||||
107
src/Umbraco.Core/Scheduling/RecurringTaskBase.cs
Normal file
107
src/Umbraco.Core/Scheduling/RecurringTaskBase.cs
Normal file
@@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides a base class for recurring background tasks.
|
||||
/// </summary>
|
||||
/// <remarks>Implement by overriding PerformRun or PerformRunAsync and then IsAsync accordingly,
|
||||
/// depending on whether the task is implemented as a sync or async method. Run nor RunAsync are
|
||||
/// sealed here as overriding them would break recurrence. And then optionally override
|
||||
/// RunsOnShutdown, in order to indicate whether the latched task should run immediately on
|
||||
/// shutdown, or just be abandoned (default).</remarks>
|
||||
public abstract class RecurringTaskBase : LatchedBackgroundTaskBase
|
||||
{
|
||||
private readonly IBackgroundTaskRunner<RecurringTaskBase> _runner;
|
||||
private readonly int _periodMilliseconds;
|
||||
private readonly Timer _timer;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RecurringTaskBase"/> class.
|
||||
/// </summary>
|
||||
/// <param name="runner">The task runner.</param>
|
||||
/// <param name="delayMilliseconds">The delay.</param>
|
||||
/// <param name="periodMilliseconds">The period.</param>
|
||||
/// <remarks>The task will repeat itself periodically. Use this constructor to create a new task.</remarks>
|
||||
protected RecurringTaskBase(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds)
|
||||
{
|
||||
_runner = runner;
|
||||
_periodMilliseconds = periodMilliseconds;
|
||||
|
||||
// note
|
||||
// must use the single-parameter constructor on Timer to avoid it from being GC'd
|
||||
// read http://stackoverflow.com/questions/4962172/why-does-a-system-timers-timer-survive-gc-but-not-system-threading-timer
|
||||
|
||||
_timer = new Timer(_ => Release());
|
||||
_timer.Change(delayMilliseconds, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements IBackgroundTask.Run().
|
||||
/// </summary>
|
||||
/// <remarks>Classes inheriting from <c>RecurringTaskBase</c> must implement <c>PerformRun</c>.</remarks>
|
||||
public sealed override void Run()
|
||||
{
|
||||
var shouldRepeat = PerformRun();
|
||||
if (shouldRepeat) Repeat();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Implements IBackgroundTask.RunAsync().
|
||||
/// </summary>
|
||||
/// <remarks>Classes inheriting from <c>RecurringTaskBase</c> must implement <c>PerformRun</c>.</remarks>
|
||||
public sealed override async Task RunAsync(CancellationToken token)
|
||||
{
|
||||
var shouldRepeat = await PerformRunAsync(token);
|
||||
if (shouldRepeat) Repeat();
|
||||
}
|
||||
|
||||
private void Repeat()
|
||||
{
|
||||
// again?
|
||||
if (_runner.IsCompleted) return; // fail fast
|
||||
|
||||
if (_periodMilliseconds == 0) return; // safe
|
||||
|
||||
Reset(); // re-latch
|
||||
|
||||
// try to add again (may fail if runner has completed)
|
||||
// if added, re-start the timer, else kill it
|
||||
if (_runner.TryAdd(this))
|
||||
_timer.Change(_periodMilliseconds, 0);
|
||||
else
|
||||
Dispose();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the background task.
|
||||
/// </summary>
|
||||
/// <returns>A value indicating whether to repeat the task.</returns>
|
||||
public virtual bool PerformRun()
|
||||
{
|
||||
throw new NotSupportedException("This task cannot run synchronously.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Runs the task asynchronously.
|
||||
/// </summary>
|
||||
/// <param name="token">A cancellation token.</param>
|
||||
/// <returns>A <see cref="Task{T}"/> instance representing the execution of the background task,
|
||||
/// and returning a value indicating whether to repeat the task.</returns>
|
||||
public virtual Task<bool> PerformRunAsync(CancellationToken token)
|
||||
{
|
||||
throw new NotSupportedException("This task cannot run asynchronously.");
|
||||
}
|
||||
|
||||
protected override void DisposeResources()
|
||||
{
|
||||
base.DisposeResources();
|
||||
|
||||
// stop the timer
|
||||
_timer.Change(Timeout.Infinite, Timeout.Infinite);
|
||||
_timer.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
62
src/Umbraco.Core/Scheduling/TaskAndFactoryExtensions.cs
Normal file
62
src/Umbraco.Core/Scheduling/TaskAndFactoryExtensions.cs
Normal file
@@ -0,0 +1,62 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
internal static class TaskAndFactoryExtensions
|
||||
{
|
||||
#region Task Extensions
|
||||
|
||||
static void SetCompletionSource<TResult>(TaskCompletionSource<TResult> completionSource, Task task)
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
completionSource.SetException(task.Exception.InnerException);
|
||||
else
|
||||
completionSource.SetResult(default(TResult));
|
||||
}
|
||||
|
||||
static void SetCompletionSource<TResult>(TaskCompletionSource<TResult> completionSource, Task<TResult> task)
|
||||
{
|
||||
if (task.IsFaulted)
|
||||
completionSource.SetException(task.Exception.InnerException);
|
||||
else
|
||||
completionSource.SetResult(task.Result);
|
||||
}
|
||||
|
||||
public static Task ContinueWithTask(this Task task, Func<Task, Task> continuation)
|
||||
{
|
||||
var completionSource = new TaskCompletionSource<object>();
|
||||
task.ContinueWith(atask => continuation(atask).ContinueWith(atask2 => SetCompletionSource(completionSource, atask2)));
|
||||
return completionSource.Task;
|
||||
}
|
||||
|
||||
public static Task ContinueWithTask(this Task task, Func<Task, Task> continuation, CancellationToken token)
|
||||
{
|
||||
var completionSource = new TaskCompletionSource<object>();
|
||||
task.ContinueWith(atask => continuation(atask).ContinueWith(atask2 => SetCompletionSource(completionSource, atask2), token), token);
|
||||
return completionSource.Task;
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region TaskFactory Extensions
|
||||
|
||||
public static Task Completed(this TaskFactory factory)
|
||||
{
|
||||
var taskSource = new TaskCompletionSource<object>();
|
||||
taskSource.SetResult(null);
|
||||
return taskSource.Task;
|
||||
}
|
||||
|
||||
public static Task Sync(this TaskFactory factory, Action action)
|
||||
{
|
||||
var taskSource = new TaskCompletionSource<object>();
|
||||
action();
|
||||
taskSource.SetResult(null);
|
||||
return taskSource.Task;
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
42
src/Umbraco.Core/Scheduling/TaskEventArgs.cs
Normal file
42
src/Umbraco.Core/Scheduling/TaskEventArgs.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides arguments for task runner events.
|
||||
/// </summary>
|
||||
/// <typeparam name="T">The type of the task.</typeparam>
|
||||
public class TaskEventArgs<T> : EventArgs
|
||||
where T : IBackgroundTask
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaskEventArgs{T}"/> class with a task.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
public TaskEventArgs(T task)
|
||||
{
|
||||
Task = task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="TaskEventArgs{T}"/> class with a task and an exception.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
/// <param name="exception">An exception.</param>
|
||||
public TaskEventArgs(T task, Exception exception)
|
||||
{
|
||||
Task = task;
|
||||
Exception = exception;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the task.
|
||||
/// </summary>
|
||||
public T Task { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the exception.
|
||||
/// </summary>
|
||||
public Exception Exception { get; private set; }
|
||||
}
|
||||
}
|
||||
75
src/Umbraco.Core/Scheduling/TempFileCleanup.cs
Normal file
75
src/Umbraco.Core/Scheduling/TempFileCleanup.cs
Normal file
@@ -0,0 +1,75 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using Umbraco.Core;
|
||||
using Umbraco.Core.Logging;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to cleanup temporary file locations
|
||||
/// </summary>
|
||||
public class TempFileCleanup : RecurringTaskBase
|
||||
{
|
||||
private readonly DirectoryInfo[] _tempFolders;
|
||||
private readonly TimeSpan _age;
|
||||
private readonly IRuntimeState _runtime;
|
||||
private readonly IProfilingLogger _logger;
|
||||
|
||||
public TempFileCleanup(IBackgroundTaskRunner<RecurringTaskBase> runner, int delayMilliseconds, int periodMilliseconds,
|
||||
IEnumerable<DirectoryInfo> tempFolders, TimeSpan age,
|
||||
IRuntimeState runtime, IProfilingLogger logger)
|
||||
: base(runner, delayMilliseconds, periodMilliseconds)
|
||||
{
|
||||
//SystemDirectories.TempFileUploads
|
||||
|
||||
_tempFolders = tempFolders.ToArray();
|
||||
_age = age;
|
||||
_runtime = runtime;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public override bool PerformRun()
|
||||
{
|
||||
// ensure we do not run if not main domain
|
||||
if (_runtime.IsMainDom == false)
|
||||
{
|
||||
_logger.Debug<TempFileCleanup>("Does not run if not MainDom.");
|
||||
return false; // do NOT repeat, going down
|
||||
}
|
||||
|
||||
foreach (var dir in _tempFolders)
|
||||
CleanupFolder(dir);
|
||||
|
||||
return true; //repeat
|
||||
}
|
||||
|
||||
private void CleanupFolder(DirectoryInfo dir)
|
||||
{
|
||||
dir.Refresh(); //in case it's changed during runtime
|
||||
if (!dir.Exists)
|
||||
{
|
||||
_logger.Debug<TempFileCleanup>("The cleanup folder doesn't exist {Folder}", dir.FullName);
|
||||
}
|
||||
|
||||
var files = dir.GetFiles("*.*", SearchOption.AllDirectories);
|
||||
foreach (var file in files)
|
||||
{
|
||||
if (DateTime.UtcNow - file.LastWriteTimeUtc > _age)
|
||||
{
|
||||
try
|
||||
{
|
||||
file.Delete();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.Error<TempFileCleanup>(ex, "Could not delete temp file {FileName}", file.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public override bool IsAsync => false;
|
||||
}
|
||||
}
|
||||
44
src/Umbraco.Core/Scheduling/ThreadingTaskImmutable.cs
Normal file
44
src/Umbraco.Core/Scheduling/ThreadingTaskImmutable.cs
Normal file
@@ -0,0 +1,44 @@
|
||||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Umbraco.Web.Scheduling
|
||||
{
|
||||
/// <summary>
|
||||
/// Wraps a <see cref="Task"/> within an object that gives access to its GetAwaiter method and Status
|
||||
/// property while ensuring that it cannot be modified in any way.
|
||||
/// </summary>
|
||||
public class ThreadingTaskImmutable
|
||||
{
|
||||
private readonly Task _task;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ThreadingTaskImmutable"/> class with a Task.
|
||||
/// </summary>
|
||||
/// <param name="task">The task.</param>
|
||||
public ThreadingTaskImmutable(Task task)
|
||||
{
|
||||
if (task == null)
|
||||
throw new ArgumentNullException("task");
|
||||
_task = task;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets an awaiter used to await the task.
|
||||
/// </summary>
|
||||
/// <returns>An awaiter instance.</returns>
|
||||
public TaskAwaiter GetAwaiter()
|
||||
{
|
||||
return _task.GetAwaiter();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets the TaskStatus of the task.
|
||||
/// </summary>
|
||||
/// <returns>The current TaskStatus of the task.</returns>
|
||||
public TaskStatus Status
|
||||
{
|
||||
get { return _task.Status; }
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user