2014-11-12 16:00:17 +11:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Concurrent;
|
2016-10-03 15:08:14 +02:00
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Runtime.CompilerServices;
|
2014-11-12 16:00:17 +11:00
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Threading.Tasks;
|
|
|
|
|
|
using System.Web.Hosting;
|
2016-09-23 16:42:42 +02:00
|
|
|
|
using Umbraco.Core;
|
2015-04-08 16:28:42 +10:00
|
|
|
|
using Umbraco.Core.Events;
|
2015-05-20 20:56:05 +02:00
|
|
|
|
using Umbraco.Core.Logging;
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Web.Scheduling
|
|
|
|
|
|
{
|
2016-09-23 16:42:42 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Manages a queue of tasks and runs them in the background.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>This class exists for logging purposes - the one you want to use is BackgroundTaskRunner{T}.</remarks>
|
|
|
|
|
|
public abstract class BackgroundTaskRunner
|
2015-05-20 20:56:05 +02:00
|
|
|
|
{ }
|
|
|
|
|
|
|
2014-11-12 16:00:17 +11:00
|
|
|
|
/// <summary>
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// Manages a queue of tasks of type <typeparamref name="T"/> and runs them in the background.
|
2014-11-12 16:00:17 +11:00
|
|
|
|
/// </summary>
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <typeparam name="T">The type of the managed tasks.</typeparam>
|
|
|
|
|
|
/// <remarks>The task runner is web-aware and will ensure that it shuts down correctly when the AppDomain
|
2015-02-17 15:09:29 +01:00
|
|
|
|
/// shuts down (ie is unloaded).</remarks>
|
2016-09-23 16:42:42 +02:00
|
|
|
|
public class BackgroundTaskRunner<T> : BackgroundTaskRunner, IBackgroundTaskRunner<T>
|
2015-02-06 16:10:34 +01:00
|
|
|
|
where T : class, IBackgroundTask
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2015-05-20 20:56:05 +02:00
|
|
|
|
private readonly string _logPrefix;
|
2015-02-04 14:59:33 +11:00
|
|
|
|
private readonly BackgroundTaskRunnerOptions _options;
|
2015-04-09 17:12:52 +10:00
|
|
|
|
private readonly ILogger _logger;
|
2015-02-06 16:10:34 +01:00
|
|
|
|
private readonly object _locker = new object();
|
2015-05-21 16:09:10 +02:00
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
private readonly BlockingCollection<T> _tasks = new BlockingCollection<T>();
|
|
|
|
|
|
private IEnumerator<T> _enumerator;
|
|
|
|
|
|
|
2015-05-21 16:09:10 +02:00
|
|
|
|
// that event is used to stop the pump when it is alive and waiting
|
|
|
|
|
|
// on a latched task - so it waits on the latch, the cancellation token,
|
|
|
|
|
|
// and the completed event
|
2015-02-08 16:25:30 +01:00
|
|
|
|
private readonly ManualResetEventSlim _completedEvent = new ManualResetEventSlim(false);
|
2015-02-06 16:10:34 +01:00
|
|
|
|
|
2015-05-21 21:54:30 +02:00
|
|
|
|
// in various places we are testing these vars outside a lock, so make them volatile
|
2015-02-06 16:10:34 +01:00
|
|
|
|
private volatile bool _isRunning; // is running
|
2016-10-03 15:08:14 +02:00
|
|
|
|
private volatile bool _completed; // does not accept tasks anymore, may still be running
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
private Task _runningTask; // the threading task that is currently executing background tasks
|
|
|
|
|
|
private CancellationTokenSource _shutdownTokenSource; // used to cancel everything and shutdown
|
|
|
|
|
|
private CancellationTokenSource _cancelTokenSource; // used to cancel the current task
|
|
|
|
|
|
private CancellationToken _shutdownToken;
|
2015-02-06 16:10:34 +01:00
|
|
|
|
|
2015-05-21 16:09:10 +02:00
|
|
|
|
private bool _terminating; // ensures we raise that event only once
|
|
|
|
|
|
private bool _terminated; // remember we've terminated
|
2016-10-03 15:08:14 +02:00
|
|
|
|
private TaskCompletionSource<int> _terminatedSource; // enable awaiting termination
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class.
|
|
|
|
|
|
/// </summary>
|
2016-09-23 16:42:42 +02:00
|
|
|
|
/// <param name="logger">A logger.</param>
|
|
|
|
|
|
/// <param name="mainDomInstall">An optional action to execute when the main domain status is aquired.</param>
|
|
|
|
|
|
/// <param name="mainDomRelease">An optional action to execute when the main domain status is released.</param>
|
|
|
|
|
|
public BackgroundTaskRunner(ILogger logger, Action mainDomInstall = null, Action mainDomRelease = null)
|
|
|
|
|
|
: this(typeof (T).FullName, new BackgroundTaskRunnerOptions(), logger, mainDomInstall, mainDomRelease)
|
2015-05-20 20:56:05 +02:00
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="name">The name of the runner.</param>
|
2016-09-23 16:42:42 +02:00
|
|
|
|
/// <param name="logger">A logger.</param>
|
|
|
|
|
|
/// <param name="mainDomInstall">An optional action to execute when the main domain status is aquired.</param>
|
|
|
|
|
|
/// <param name="mainDomRelease">An optional action to execute when the main domain status is released.</param>
|
|
|
|
|
|
public BackgroundTaskRunner(string name, ILogger logger, Action mainDomInstall = null, Action mainDomRelease = null)
|
|
|
|
|
|
: this(name, new BackgroundTaskRunnerOptions(), logger, mainDomInstall, mainDomRelease)
|
2015-02-06 16:10:34 +01:00
|
|
|
|
{ }
|
2015-02-04 14:59:33 +11:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class with a set of options.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="options">The set of options.</param>
|
2016-09-23 16:42:42 +02:00
|
|
|
|
/// <param name="logger">A logger.</param>
|
|
|
|
|
|
/// <param name="mainDomInstall">An optional action to execute when the main domain status is aquired.</param>
|
|
|
|
|
|
/// <param name="mainDomRelease">An optional action to execute when the main domain status is released.</param>
|
|
|
|
|
|
public BackgroundTaskRunner(BackgroundTaskRunnerOptions options, ILogger logger, Action mainDomInstall = null, Action mainDomRelease = null)
|
|
|
|
|
|
: this(typeof (T).FullName, options, logger, mainDomInstall, mainDomRelease)
|
2015-05-20 20:56:05 +02:00
|
|
|
|
{ }
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Initializes a new instance of the <see cref="BackgroundTaskRunner{T}"/> class with a set of options.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="name">The name of the runner.</param>
|
|
|
|
|
|
/// <param name="options">The set of options.</param>
|
2016-09-23 16:42:42 +02:00
|
|
|
|
/// <param name="logger">A logger.</param>
|
|
|
|
|
|
/// <param name="mainDomInstall">An optional action to execute when the main domain status is aquired.</param>
|
|
|
|
|
|
/// <param name="mainDomRelease">An optional action to execute when the main domain status is released.</param>
|
|
|
|
|
|
public BackgroundTaskRunner(string name, BackgroundTaskRunnerOptions options, ILogger logger, Action mainDomInstall = null, Action mainDomRelease = null)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2015-02-04 14:59:33 +11:00
|
|
|
|
if (options == null) throw new ArgumentNullException("options");
|
2015-04-09 17:12:52 +10:00
|
|
|
|
if (logger == null) throw new ArgumentNullException("logger");
|
2015-02-04 14:59:33 +11:00
|
|
|
|
_options = options;
|
2015-05-20 20:56:05 +02:00
|
|
|
|
_logPrefix = "[" + name + "] ";
|
2015-04-09 17:12:52 +10:00
|
|
|
|
_logger = logger;
|
|
|
|
|
|
|
2015-07-10 15:52:58 +02:00
|
|
|
|
if (options.Hosted)
|
|
|
|
|
|
HostingEnvironment.RegisterObject(this);
|
2015-02-06 16:10:34 +01:00
|
|
|
|
|
2016-09-29 13:53:35 +02:00
|
|
|
|
if (mainDomInstall != null || mainDomRelease != null)
|
2016-09-23 16:42:42 +02:00
|
|
|
|
{
|
2016-09-29 13:53:35 +02:00
|
|
|
|
var appContext = ApplicationContext.Current;
|
|
|
|
|
|
var mainDom = appContext == null ? null : appContext.MainDom;
|
2016-09-23 16:42:42 +02:00
|
|
|
|
var reg = mainDom == null || ApplicationContext.Current.MainDom.Register(mainDomInstall, mainDomRelease);
|
|
|
|
|
|
if (reg == false)
|
2016-10-03 15:08:14 +02:00
|
|
|
|
_completed = _terminated = true;
|
2016-09-29 13:53:35 +02:00
|
|
|
|
if (reg && mainDom == null && mainDomInstall != null)
|
|
|
|
|
|
mainDomInstall();
|
2016-09-23 16:42:42 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (options.AutoStart && _terminated == false)
|
2015-02-06 16:10:34 +01:00
|
|
|
|
StartUp();
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets the number of tasks in the queue.
|
|
|
|
|
|
/// </summary>
|
2014-11-12 16:00:17 +11:00
|
|
|
|
public int TaskCount
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _tasks.Count; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <summary>
|
2016-10-03 15:08:14 +02:00
|
|
|
|
/// Gets a value indicating whether a threading task is currently running.
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// </summary>
|
2014-11-12 16:00:17 +11:00
|
|
|
|
public bool IsRunning
|
|
|
|
|
|
{
|
|
|
|
|
|
get { return _isRunning; }
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Gets a value indicating whether the runner has completed and cannot accept tasks anymore.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
public bool IsCompleted
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
get { return _completed; }
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <summary>
|
2016-10-03 15:08:14 +02:00
|
|
|
|
/// Gets the running threading task as an immutable awaitable.
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">There is no running task.</exception>
|
2015-04-08 16:28:42 +10:00
|
|
|
|
/// <remarks>
|
2016-10-03 15:08:14 +02:00
|
|
|
|
/// <para>Unless the AutoStart option is true, there will be no current threading task until
|
|
|
|
|
|
/// a background task is added to the queue, and there will be no current threading task
|
|
|
|
|
|
/// when the queue is empty. In which case this method returns null.</para>
|
|
|
|
|
|
/// <para>The returned value can be awaited and that is all (eg no continuation).</para>
|
2015-04-08 16:28:42 +10:00
|
|
|
|
/// </remarks>
|
2016-10-03 15:08:14 +02:00
|
|
|
|
internal ThreadingTaskImmutable CurrentThreadingTask
|
2015-02-06 16:10:34 +01:00
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
2015-05-21 21:54:30 +02:00
|
|
|
|
lock (_locker)
|
|
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
return _runningTask == null ? null : new ThreadingTaskImmutable(_runningTask);
|
2015-05-21 21:54:30 +02:00
|
|
|
|
}
|
2015-02-06 16:10:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2015-04-08 16:28:42 +10:00
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
// fixme should the above throw, return null, a completed task?
|
|
|
|
|
|
|
|
|
|
|
|
// fixme what's the diff?!
|
2014-11-12 16:00:17 +11:00
|
|
|
|
/// <summary>
|
2015-05-21 16:09:10 +02:00
|
|
|
|
/// Gets an awaitable used to await the runner running operation.
|
2014-11-12 16:00:17 +11:00
|
|
|
|
/// </summary>
|
2015-05-21 16:09:10 +02:00
|
|
|
|
/// <returns>An awaitable instance.</returns>
|
|
|
|
|
|
/// <remarks>Used to wait until the runner is no longer running (IsRunning == false),
|
|
|
|
|
|
/// though the runner could be started again afterwards by adding tasks to it.</remarks>
|
2016-10-03 15:08:14 +02:00
|
|
|
|
internal ThreadingTaskImmutable StoppedAwaitable
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2015-05-21 16:09:10 +02:00
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_locker)
|
|
|
|
|
|
{
|
|
|
|
|
|
var task = _runningTask ?? Task.FromResult(0);
|
|
|
|
|
|
return new ThreadingTaskImmutable(task);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2016-10-03 15:08:14 +02:00
|
|
|
|
/// Gets an awaitable object that can be used to await for the runner to terminate.
|
2015-05-21 16:09:10 +02:00
|
|
|
|
/// </summary>
|
2016-10-03 15:08:14 +02:00
|
|
|
|
/// <returns>An awaitable object.</returns>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// <para>Used to wait until the runner has terminated.</para>
|
|
|
|
|
|
/// <para>This is for unit tests and should not be used otherwise. In most cases when the runner
|
|
|
|
|
|
/// has terminated, the application domain is going down and it is not the right time to do things.</para>
|
|
|
|
|
|
/// </remarks>
|
|
|
|
|
|
internal ThreadingTaskImmutable TerminatedAwaitable
|
2015-05-21 16:09:10 +02:00
|
|
|
|
{
|
|
|
|
|
|
get
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_locker)
|
|
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
if (_terminatedSource == null)
|
2015-05-21 16:09:10 +02:00
|
|
|
|
_terminatedSource = new TaskCompletionSource<int>();
|
2016-10-03 15:08:14 +02:00
|
|
|
|
if (_terminated)
|
|
|
|
|
|
_terminatedSource.SetResult(0);
|
|
|
|
|
|
return new ThreadingTaskImmutable(_terminatedSource.Task);
|
2015-05-21 16:09:10 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Adds a task to the queue.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="task">The task to add.</param>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">The task runner has completed.</exception>
|
2014-11-12 16:00:17 +11:00
|
|
|
|
public void Add(T task)
|
|
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
lock (_locker)
|
|
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
if (_completed)
|
2015-02-06 16:10:34 +01:00
|
|
|
|
throw new InvalidOperationException("The task runner has completed.");
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// add task
|
2015-05-21 17:12:30 +10:00
|
|
|
|
_logger.Debug<BackgroundTaskRunner>(_logPrefix + "Task added {0}", task.GetType);
|
2015-02-06 16:10:34 +01:00
|
|
|
|
_tasks.Add(task);
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// start
|
|
|
|
|
|
StartUpLocked();
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Tries to add a task to the queue.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="task">The task to add.</param>
|
|
|
|
|
|
/// <returns>true if the task could be added to the queue; otherwise false.</returns>
|
|
|
|
|
|
/// <remarks>Returns false if the runner is completed.</remarks>
|
|
|
|
|
|
public bool TryAdd(T task)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
lock (_locker)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
if (_completed)
|
2016-03-10 15:17:46 +01:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
_logger.Debug<BackgroundTaskRunner>(_logPrefix + "Task cannot be added {0}, the task runner has already shutdown", task.GetType);
|
2016-03-10 15:17:46 +01:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// add task
|
2015-05-21 17:12:30 +10:00
|
|
|
|
_logger.Debug<BackgroundTaskRunner>(_logPrefix + "Task added {0}", task.GetType);
|
2015-02-06 16:10:34 +01:00
|
|
|
|
_tasks.Add(task);
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// start
|
|
|
|
|
|
StartUpLocked();
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
return true;
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Cancels to current task, if any.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <remarks>Has no effect if the task runs synchronously, or does not want to cancel.</remarks>
|
|
|
|
|
|
public void CancelCurrentBackgroundTask()
|
|
|
|
|
|
{
|
|
|
|
|
|
lock (_locker)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_completed)
|
|
|
|
|
|
throw new InvalidOperationException("The task runner has completed.");
|
|
|
|
|
|
if (_cancelTokenSource != null)
|
|
|
|
|
|
_cancelTokenSource.Cancel();
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-11-12 16:00:17 +11:00
|
|
|
|
/// <summary>
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// Starts the tasks runner, if not already running.
|
2014-11-12 16:00:17 +11:00
|
|
|
|
/// </summary>
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <remarks>Is invoked each time a task is added, to ensure it is going to be processed.</remarks>
|
|
|
|
|
|
/// <exception cref="InvalidOperationException">The task runner has completed.</exception>
|
2016-10-03 15:08:14 +02:00
|
|
|
|
internal void StartUp()
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
if (_isRunning) return;
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
lock (_locker)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
if (_completed)
|
2015-02-06 16:10:34 +01:00
|
|
|
|
throw new InvalidOperationException("The task runner has completed.");
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
StartUpLocked();
|
|
|
|
|
|
}
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// Starts the tasks runner, if not already running.
|
2014-11-12 16:00:17 +11:00
|
|
|
|
/// </summary>
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <remarks>Must be invoked within lock(_locker) and with _isCompleted being false.</remarks>
|
|
|
|
|
|
private void StartUpLocked()
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// double check
|
|
|
|
|
|
if (_isRunning) return;
|
|
|
|
|
|
_isRunning = true;
|
|
|
|
|
|
|
|
|
|
|
|
// create a new token source since this is a new process
|
2016-10-03 15:08:14 +02:00
|
|
|
|
_shutdownTokenSource = new CancellationTokenSource();
|
|
|
|
|
|
_shutdownToken = _shutdownTokenSource.Token;
|
|
|
|
|
|
|
|
|
|
|
|
_enumerator = _options.KeepAlive ? _tasks.GetConsumingEnumerable(_shutdownToken).GetEnumerator() : null;
|
|
|
|
|
|
_runningTask = Task.Run(async () => await Pump(), _shutdownToken);
|
|
|
|
|
|
|
2015-05-21 17:12:30 +10:00
|
|
|
|
_logger.Debug<BackgroundTaskRunner>(_logPrefix + "Starting");
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/// <summary>
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// Shuts the taks runner down.
|
2014-11-12 16:00:17 +11:00
|
|
|
|
/// </summary>
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <param name="force">True for force the runner to stop.</param>
|
|
|
|
|
|
/// <param name="wait">True to wait until the runner has stopped.</param>
|
|
|
|
|
|
/// <remarks>If <paramref name="force"/> is false, no more tasks can be queued but all queued tasks
|
|
|
|
|
|
/// will run. If it is true, then only the current one (if any) will end and no other task will run.</remarks>
|
|
|
|
|
|
public void Shutdown(bool force, bool wait)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
lock (_locker)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
_completed = true; // do not accept new tasks
|
2015-02-06 16:10:34 +01:00
|
|
|
|
if (_isRunning == false) return; // done already
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// try to be nice
|
|
|
|
|
|
// assuming multiple threads can do these without problems
|
|
|
|
|
|
_completedEvent.Set();
|
|
|
|
|
|
_tasks.CompleteAdding();
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
if (force)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// we must bring everything down, now
|
2015-05-21 16:09:10 +02:00
|
|
|
|
Thread.Sleep(100); // give time to CompleteAdding()
|
2015-02-06 16:10:34 +01:00
|
|
|
|
lock (_locker)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// was CompleteAdding() enough?
|
|
|
|
|
|
if (_isRunning == false) return;
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// try to cancel running async tasks (cannot do much about sync tasks)
|
|
|
|
|
|
// break delayed tasks delay
|
|
|
|
|
|
// truncate running queues
|
2016-10-03 15:08:14 +02:00
|
|
|
|
_shutdownTokenSource.Cancel(false); // false is the default
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
2015-02-17 15:05:42 +01:00
|
|
|
|
|
|
|
|
|
|
// tasks in the queue will be executed...
|
|
|
|
|
|
if (wait == false) return;
|
2016-05-12 12:27:02 +02:00
|
|
|
|
|
|
|
|
|
|
if (_runningTask != null)
|
|
|
|
|
|
_runningTask.Wait(); // wait for whatever is running to end...
|
2015-02-06 16:10:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
private async Task Pump()
|
2015-02-06 16:10:34 +01:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
while (true)
|
2015-02-06 16:10:34 +01:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
var bgTask = GetNextBackgroundTask();
|
|
|
|
|
|
if (bgTask == null)
|
|
|
|
|
|
return;
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
lock (_locker)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
// set another one - for the next task
|
|
|
|
|
|
_cancelTokenSource = CancellationTokenSource.CreateLinkedTokenSource(_shutdownToken);
|
2015-02-06 16:10:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
bgTask = WaitForLatch(bgTask, _cancelTokenSource.Token);
|
|
|
|
|
|
if (bgTask == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
2015-05-21 16:09:10 +02:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
await RunAsync(bgTask, _cancelTokenSource.Token).ConfigureAwait(false);
|
2015-05-21 16:09:10 +02:00
|
|
|
|
}
|
2016-10-03 15:08:14 +02:00
|
|
|
|
catch (Exception e)
|
2015-02-06 16:10:34 +01:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
// RunAsync should NOT throw exception - just raise an event
|
|
|
|
|
|
// this is here for safety and to ensure we don't kill everything, ever
|
|
|
|
|
|
_logger.Error<BackgroundTaskRunner>(_logPrefix + "Task runner exception.", e);
|
2015-02-06 16:10:34 +01:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
_cancelTokenSource = null;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2015-02-06 16:10:34 +01:00
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
private T GetNextBackgroundTask()
|
|
|
|
|
|
{
|
|
|
|
|
|
while (true)
|
|
|
|
|
|
{
|
|
|
|
|
|
// exit if cancelling
|
|
|
|
|
|
if (_shutdownToken.IsCancellationRequested == false)
|
2015-02-06 16:10:34 +01:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
// try to get a task
|
|
|
|
|
|
// the blocking MoveNext will end if token is cancelled or collection is completed
|
|
|
|
|
|
T bgTask;
|
|
|
|
|
|
var hasBgTask = _options.KeepAlive
|
|
|
|
|
|
? (bgTask = _enumerator.MoveNext() ? _enumerator.Current : null) != null // blocking
|
|
|
|
|
|
: _tasks.TryTake(out bgTask); // non-blocking
|
|
|
|
|
|
|
|
|
|
|
|
// exit if cancelling
|
|
|
|
|
|
if (_shutdownToken.IsCancellationRequested == false && hasBgTask)
|
|
|
|
|
|
return bgTask;
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
lock (_locker)
|
2015-02-06 16:10:34 +01:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
if (_shutdownToken.IsCancellationRequested == false && _tasks.Count > 0) continue;
|
2015-02-06 16:10:34 +01:00
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
_logger.Debug<BackgroundTaskRunner>(_logPrefix + "Stopping");
|
2015-02-06 16:10:34 +01:00
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
if (_options.PreserveRunningTask == false)
|
|
|
|
|
|
_runningTask = null;
|
2015-02-06 16:10:34 +01:00
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
_isRunning = false;
|
2015-02-06 16:10:34 +01:00
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
_shutdownToken = CancellationToken.None;
|
|
|
|
|
|
_enumerator = null;
|
|
|
|
|
|
}
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
OnEvent(Stopped, "Stopped");
|
|
|
|
|
|
return null;
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
2015-02-06 16:10:34 +01:00
|
|
|
|
}
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
private T WaitForLatch(T bgTask, CancellationToken token)
|
2015-02-06 16:10:34 +01:00
|
|
|
|
{
|
2016-10-03 15:08:14 +02:00
|
|
|
|
var latched = bgTask as ILatchedBackgroundTask;
|
|
|
|
|
|
if (latched == null || latched.IsLatched == false) return bgTask;
|
|
|
|
|
|
|
|
|
|
|
|
// returns the array index of the object that satisfied the wait
|
|
|
|
|
|
var i = WaitHandle.WaitAny(new[] { latched.Latch, token.WaitHandle, _completedEvent.WaitHandle });
|
|
|
|
|
|
|
|
|
|
|
|
switch (i)
|
|
|
|
|
|
{
|
|
|
|
|
|
case 0:
|
|
|
|
|
|
// ok to run now
|
|
|
|
|
|
return bgTask;
|
|
|
|
|
|
case 1:
|
|
|
|
|
|
// cancellation
|
|
|
|
|
|
return null;
|
|
|
|
|
|
case 2:
|
|
|
|
|
|
// termination
|
|
|
|
|
|
if (latched.RunsOnShutdown) return bgTask;
|
|
|
|
|
|
latched.Dispose();
|
|
|
|
|
|
return null;
|
|
|
|
|
|
default:
|
|
|
|
|
|
throw new Exception("panic.");
|
|
|
|
|
|
}
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
private async Task RunAsync(T bgTask, CancellationToken token)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
OnTaskStarting(new TaskEventArgs<T>(bgTask));
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
2015-07-06 16:56:01 +02:00
|
|
|
|
try
|
2014-11-12 16:00:17 +11:00
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
if (bgTask.IsAsync)
|
2015-04-08 17:15:21 +10:00
|
|
|
|
//configure await = false since we don't care about the context, we're on a background thread.
|
|
|
|
|
|
await bgTask.RunAsync(token).ConfigureAwait(false);
|
2015-01-29 12:45:44 +11:00
|
|
|
|
else
|
2015-02-06 16:10:34 +01:00
|
|
|
|
bgTask.Run();
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
2016-10-03 15:08:14 +02:00
|
|
|
|
finally // ensure we disposed - unless latched again ie wants to re-run
|
2015-07-06 16:56:01 +02:00
|
|
|
|
{
|
|
|
|
|
|
var lbgTask = bgTask as ILatchedBackgroundTask;
|
|
|
|
|
|
if (lbgTask == null || lbgTask.IsLatched == false)
|
|
|
|
|
|
bgTask.Dispose();
|
|
|
|
|
|
}
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception e)
|
|
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
OnTaskError(new TaskEventArgs<T>(bgTask, e));
|
2014-11-12 16:00:17 +11:00
|
|
|
|
throw;
|
|
|
|
|
|
}
|
2015-02-08 16:25:30 +01:00
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
OnTaskCompleted(new TaskEventArgs<T>(bgTask));
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2015-05-21 20:49:18 +02:00
|
|
|
|
_logger.Error<BackgroundTaskRunner>(_logPrefix + "Task has failed", ex);
|
2016-10-03 15:08:14 +02:00
|
|
|
|
}
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
#region Events
|
|
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
// triggers when a background task starts
|
|
|
|
|
|
public event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskStarting;
|
|
|
|
|
|
|
|
|
|
|
|
// triggers when a background task has completed
|
|
|
|
|
|
public event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskCompleted;
|
|
|
|
|
|
|
|
|
|
|
|
// triggers when a background task throws
|
|
|
|
|
|
public event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskError;
|
|
|
|
|
|
|
|
|
|
|
|
// triggers when a background task is cancelled
|
|
|
|
|
|
public event TypedEventHandler<BackgroundTaskRunner<T>, TaskEventArgs<T>> TaskCancelled;
|
|
|
|
|
|
|
|
|
|
|
|
// triggers when the runner stops (but could start again if a task is added to it)
|
|
|
|
|
|
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Stopped;
|
|
|
|
|
|
|
|
|
|
|
|
// triggers when the hosting environment requests that the runner terminates
|
|
|
|
|
|
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Terminating;
|
|
|
|
|
|
|
|
|
|
|
|
// triggers when the runner has terminated (no task can be added, no task is running)
|
|
|
|
|
|
internal event TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> Terminated;
|
|
|
|
|
|
|
2015-05-21 16:09:10 +02:00
|
|
|
|
private void OnEvent(TypedEventHandler<BackgroundTaskRunner<T>, EventArgs> handler, string name)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (handler == null) return;
|
|
|
|
|
|
OnEvent(handler, name, EventArgs.Empty);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-05-20 20:56:05 +02:00
|
|
|
|
private void OnEvent<TArgs>(TypedEventHandler<BackgroundTaskRunner<T>, TArgs> handler, string name, TArgs e)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (handler == null) return;
|
|
|
|
|
|
|
|
|
|
|
|
try
|
|
|
|
|
|
{
|
|
|
|
|
|
handler(this, e);
|
|
|
|
|
|
}
|
|
|
|
|
|
catch (Exception ex)
|
|
|
|
|
|
{
|
2015-05-21 17:12:30 +10:00
|
|
|
|
_logger.Error<BackgroundTaskRunner>(_logPrefix + name + " exception occurred", ex);
|
2015-05-20 20:56:05 +02:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-11-12 16:00:17 +11:00
|
|
|
|
protected virtual void OnTaskError(TaskEventArgs<T> e)
|
|
|
|
|
|
{
|
2015-05-20 20:56:05 +02:00
|
|
|
|
OnEvent(TaskError, "TaskError", e);
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void OnTaskStarting(TaskEventArgs<T> e)
|
|
|
|
|
|
{
|
2015-05-20 20:56:05 +02:00
|
|
|
|
OnEvent(TaskStarting, "TaskStarting", e);
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void OnTaskCompleted(TaskEventArgs<T> e)
|
|
|
|
|
|
{
|
2015-05-20 20:56:05 +02:00
|
|
|
|
OnEvent(TaskCompleted, "TaskCompleted", e);
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void OnTaskCancelled(TaskEventArgs<T> e)
|
|
|
|
|
|
{
|
2015-05-20 20:56:05 +02:00
|
|
|
|
OnEvent(TaskCancelled, "TaskCancelled", e);
|
2015-02-04 15:12:37 +11:00
|
|
|
|
|
|
|
|
|
|
//dispose it
|
|
|
|
|
|
e.Task.Dispose();
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
|
|
|
|
|
#region IDisposable
|
2014-11-12 16:00:17 +11:00
|
|
|
|
|
|
|
|
|
|
private readonly object _disposalLocker = new object();
|
|
|
|
|
|
public bool IsDisposed { get; private set; }
|
|
|
|
|
|
|
|
|
|
|
|
~BackgroundTaskRunner()
|
|
|
|
|
|
{
|
2015-05-20 20:56:05 +02:00
|
|
|
|
Dispose(false);
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public void Dispose()
|
|
|
|
|
|
{
|
|
|
|
|
|
Dispose(true);
|
|
|
|
|
|
GC.SuppressFinalize(this);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void Dispose(bool disposing)
|
|
|
|
|
|
{
|
2015-05-20 20:56:05 +02:00
|
|
|
|
if (IsDisposed || disposing == false)
|
2014-11-12 16:00:17 +11:00
|
|
|
|
return;
|
2015-02-06 16:10:34 +01:00
|
|
|
|
|
2014-11-12 16:00:17 +11:00
|
|
|
|
lock (_disposalLocker)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (IsDisposed)
|
|
|
|
|
|
return;
|
|
|
|
|
|
DisposeResources();
|
|
|
|
|
|
IsDisposed = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
protected virtual void DisposeResources()
|
|
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// just make sure we eventually go down
|
|
|
|
|
|
Shutdown(true, false);
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
2015-02-06 16:10:34 +01:00
|
|
|
|
|
2014-11-12 16:00:17 +11:00
|
|
|
|
#endregion
|
|
|
|
|
|
|
2015-02-06 16:10:34 +01:00
|
|
|
|
/// <summary>
|
|
|
|
|
|
/// Requests a registered object to unregister.
|
|
|
|
|
|
/// </summary>
|
|
|
|
|
|
/// <param name="immediate">true to indicate the registered object should unregister from the hosting
|
|
|
|
|
|
/// environment before returning; otherwise, false.</param>
|
|
|
|
|
|
/// <remarks>
|
|
|
|
|
|
/// <para>"When the application manager needs to stop a registered object, it will call the Stop method."</para>
|
|
|
|
|
|
/// <para>The application manager will call the Stop method to ask a registered object to unregister. During
|
|
|
|
|
|
/// processing of the Stop method, the registered object must call the HostingEnvironment.UnregisterObject method.</para>
|
|
|
|
|
|
/// </remarks>
|
2014-11-12 16:00:17 +11:00
|
|
|
|
public void Stop(bool immediate)
|
|
|
|
|
|
{
|
2015-05-21 16:09:10 +02:00
|
|
|
|
// the first time the hosting environment requests that the runner terminates,
|
|
|
|
|
|
// raise the Terminating event - that could be used to prevent any process that
|
|
|
|
|
|
// would expect the runner to be available from starting.
|
|
|
|
|
|
var onTerminating = false;
|
|
|
|
|
|
lock (_locker)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_terminating == false)
|
|
|
|
|
|
{
|
|
|
|
|
|
_terminating = true;
|
2015-05-21 20:49:18 +02:00
|
|
|
|
_logger.Info<BackgroundTaskRunner>(_logPrefix + "Terminating" + (immediate ? " (immediate)" : ""));
|
2015-05-21 16:09:10 +02:00
|
|
|
|
onTerminating = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (onTerminating)
|
|
|
|
|
|
OnEvent(Terminating, "Terminating");
|
|
|
|
|
|
|
2014-11-12 16:00:17 +11:00
|
|
|
|
if (immediate == false)
|
|
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// The Stop method is first called with the immediate parameter set to false. The object can either complete
|
|
|
|
|
|
// processing, call the UnregisterObject method, and then return or it can return immediately and complete
|
|
|
|
|
|
// processing asynchronously before calling the UnregisterObject method.
|
|
|
|
|
|
|
2015-05-21 20:49:18 +02:00
|
|
|
|
_logger.Info<BackgroundTaskRunner>(_logPrefix + "Waiting for tasks to complete");
|
2015-02-06 16:10:34 +01:00
|
|
|
|
Shutdown(false, false); // do not accept any more tasks, flush the queue, do not wait
|
|
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
// raise the completed event only after the running threading task has completed
|
2015-02-06 16:10:34 +01:00
|
|
|
|
lock (_locker)
|
|
|
|
|
|
{
|
|
|
|
|
|
if (_runningTask != null)
|
2015-05-21 16:09:10 +02:00
|
|
|
|
_runningTask.ContinueWith(_ => Terminate(false));
|
2015-02-06 16:10:34 +01:00
|
|
|
|
else
|
2015-05-21 16:09:10 +02:00
|
|
|
|
Terminate(false);
|
2015-02-06 16:10:34 +01:00
|
|
|
|
}
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
else
|
|
|
|
|
|
{
|
2015-02-06 16:10:34 +01:00
|
|
|
|
// If the registered object does not complete processing before the application manager's time-out
|
|
|
|
|
|
// period expires, the Stop method is called again with the immediate parameter set to true. When the
|
|
|
|
|
|
// immediate parameter is true, the registered object must call the UnregisterObject method before returning;
|
|
|
|
|
|
// otherwise, its registration will be removed by the application manager.
|
|
|
|
|
|
|
2015-05-21 20:49:18 +02:00
|
|
|
|
_logger.Info<BackgroundTaskRunner>(_logPrefix + "Cancelling tasks");
|
2015-02-06 16:10:34 +01:00
|
|
|
|
Shutdown(true, true); // cancel all tasks, wait for the current one to end
|
2015-05-21 16:09:10 +02:00
|
|
|
|
Terminate(true);
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-10-03 15:08:14 +02:00
|
|
|
|
// called by Stop either immediately or eventually
|
2015-05-21 16:09:10 +02:00
|
|
|
|
private void Terminate(bool immediate)
|
|
|
|
|
|
{
|
|
|
|
|
|
// signal the environment we have terminated
|
|
|
|
|
|
// log
|
|
|
|
|
|
// raise the Terminated event
|
|
|
|
|
|
// complete the awaitable completion source, if any
|
|
|
|
|
|
|
|
|
|
|
|
HostingEnvironment.UnregisterObject(this);
|
|
|
|
|
|
|
|
|
|
|
|
TaskCompletionSource<int> terminatedSource;
|
|
|
|
|
|
lock (_locker)
|
|
|
|
|
|
{
|
|
|
|
|
|
_terminated = true;
|
|
|
|
|
|
terminatedSource = _terminatedSource;
|
2015-05-19 20:08:52 +02:00
|
|
|
|
}
|
2016-10-03 15:08:14 +02:00
|
|
|
|
|
|
|
|
|
|
_logger.Info<BackgroundTaskRunner>(_logPrefix + "Tasks " + (immediate ? "cancelled" : "completed") + ", terminated");
|
|
|
|
|
|
|
|
|
|
|
|
OnEvent(Terminated, "Terminated");
|
|
|
|
|
|
|
2015-05-21 16:09:10 +02:00
|
|
|
|
if (terminatedSource != null)
|
|
|
|
|
|
terminatedSource.SetResult(0);
|
2015-05-19 20:08:52 +02:00
|
|
|
|
}
|
2014-11-12 16:00:17 +11:00
|
|
|
|
}
|
|
|
|
|
|
}
|