2014-11-12 16:00:17 +11:00
using System ;
using System.Threading ;
using System.Threading.Tasks ;
2016-10-04 14:10:20 +02:00
using System.Threading.Tasks.Dataflow ;
2014-11-12 16:00:17 +11:00
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
2019-01-08 20:49:21 +01:00
{
/// <summary>
2019-01-09 10:04:31 +01:00
/// Represents a MainDom hook.
2019-01-08 20:49:21 +01:00
/// </summary>
public class MainDomHook
{
2019-01-09 10:04:31 +01:00
/// <summary>
/// Initializes a new instance of the <see cref="MainDomHook"/> class.
/// </summary>
/// <param name="mainDom">The <see cref="IMainDom"/> object.</param>
/// <param name="install">A method to execute when hooking into the main domain.</param>
/// <param name="release">A method to execute when the main domain releases.</param>
2019-01-08 20:49:21 +01:00
public MainDomHook ( IMainDom mainDom , Action install , Action release )
{
MainDom = mainDom ;
Install = install ;
Release = release ;
}
2019-01-09 10:04:31 +01:00
/// <summary>
/// Gets the <see cref="IMainDom"/> object.
/// </summary>
2019-01-08 20:49:21 +01:00
public IMainDom MainDom { get ; }
2019-01-09 10:04:31 +01:00
/// <summary>
/// Gets the method to execute when hooking into the main domain.
/// </summary>
2019-01-08 20:49:21 +01:00
public Action Install { get ; }
2019-01-09 10:04:31 +01:00
/// <summary>
/// Gets the method to execute when the main domain releases.
/// </summary>
2019-01-08 20:49:21 +01:00
public Action Release { get ; }
internal bool Register ( )
{
if ( MainDom ! = null )
return MainDom . Register ( Install , Release ) ;
// tests
Install ? . Invoke ( ) ;
return true ;
}
}
}
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
{
2016-10-04 14:10:20 +02:00
// do not remove this comment!
//
// if you plan to do anything on this class, first go and read
// http://blog.stephencleary.com/2012/12/dont-block-in-asynchronous-code.html
// http://stackoverflow.com/questions/19481964/calling-taskcompletionsource-setresult-in-a-non-blocking-manner
// http://stackoverflow.com/questions/21225361/is-there-anything-like-asynchronous-blockingcollectiont
// and more, and more, and more
// and remember: async is hard
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-11-03 10:31:44 +01:00
private readonly BufferBlock < T > _tasks = new BufferBlock < T > ( new DataflowBlockOptions ( ) ) ;
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-05 15:07:47 +02:00
private readonly TaskCompletionSource < int > _terminatedSource = new TaskCompletionSource < int > ( ) ; // enable awaiting termination
2015-05-21 16:09:10 +02: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>
2016-11-03 10:31:44 +01:00
/// <param name="hook">An optional main domain hook.</param>
public BackgroundTaskRunner ( ILogger logger , MainDomHook hook = null )
: this ( typeof ( T ) . FullName , new BackgroundTaskRunnerOptions ( ) , logger , hook )
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>
2016-11-03 10:31:44 +01:00
/// <param name="hook">An optional main domain hook.</param>
public BackgroundTaskRunner ( string name , ILogger logger , MainDomHook hook = null )
: this ( name , new BackgroundTaskRunnerOptions ( ) , logger , hook )
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>
2016-11-03 10:31:44 +01:00
/// <param name="hook">An optional main domain hook.</param>
public BackgroundTaskRunner ( BackgroundTaskRunnerOptions options , ILogger logger , MainDomHook hook = null )
: this ( typeof ( T ) . FullName , options , logger , hook )
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>
2016-11-03 10:31:44 +01:00
/// <param name="hook">An optional main domain hook.</param>
public BackgroundTaskRunner ( string name , BackgroundTaskRunnerOptions options , ILogger logger , MainDomHook hook = null )
2014-11-12 16:00:17 +11:00
{
2019-01-08 20:49:21 +01:00
_options = options ? ? throw new ArgumentNullException ( nameof ( options ) ) ;
_logger = logger ? ? throw new ArgumentNullException ( nameof ( logger ) ) ;
2015-05-20 20:56:05 +02:00
_logPrefix = "[" + name + "] " ;
2015-04-09 17:12:52 +10:00
2015-07-10 15:52:58 +02:00
if ( options . Hosted )
HostingEnvironment . RegisterObject ( this ) ;
2015-02-06 16:10:34 +01:00
2016-11-03 10:31:44 +01:00
if ( hook ! = null )
_completed = _terminated = hook . Register ( ) = = false ;
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>
2016-11-03 10:31:44 +01:00
public int TaskCount = > _tasks . Count ;
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 a value indicating whether a threading task is currently running.
2015-02-06 16:10:34 +01:00
/// </summary>
2016-11-03 10:31:44 +01:00
public bool IsRunning = > _isRunning ;
2014-11-12 16:00:17 +11:00
2015-02-06 16:10:34 +01:00
/// <summary>
/// Gets a value indicating whether the runner has completed and cannot accept tasks anymore.
/// </summary>
2016-11-03 10:31:44 +01:00
public bool IsCompleted = > _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
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),
2017-05-12 14:49:44 +02:00
/// though the runner could be started again afterwards by adding tasks to it. If
/// the runner is not running, returns a completed awaitable.</remarks>
2015-05-21 16:09:10 +02:00
public 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
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
2018-08-14 22:36:47 +01:00
_logger . Debug < BackgroundTaskRunner > ( "{LogPrefix} Task Added {TaskType}" , _logPrefix , task . GetType ( ) . FullName ) ;
2016-10-04 14:10:20 +02:00
_tasks . Post ( 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
{
2018-08-14 22:36:47 +01:00
_logger . Debug < BackgroundTaskRunner > ( "{LogPrefix} Task cannot be added {TaskType}, the task runner has already shutdown" , _logPrefix , task . GetType ( ) . FullName ) ;
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
2018-08-14 22:36:47 +01:00
_logger . Debug < BackgroundTaskRunner > ( "{LogPrefix} Task added {TaskType}" , _logPrefix , task . GetType ( ) . FullName ) ;
2016-10-04 14:10:20 +02:00
_tasks . Post ( 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." ) ;
2016-11-03 10:31:44 +01:00
_cancelTokenSource ? . Cancel ( ) ;
2016-10-03 15:08:14 +02:00
}
}
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
{
2017-05-12 14:49:44 +02:00
// double check
2015-02-06 16:10:34 +01:00
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 ;
2016-10-04 14:10:20 +02:00
_runningTask = Task . Run ( async ( ) = > await Pump ( ) . ConfigureAwait ( false ) , _shutdownToken ) ;
2016-10-03 15:08:14 +02:00
2018-08-14 22:36:47 +01:00
_logger . Debug < BackgroundTaskRunner > ( "{LogPrefix} Starting" , _logPrefix ) ;
2014-11-12 16:00:17 +11:00
}
/// <summary>
2019-01-08 20:49:21 +01:00
/// Shuts the tasks 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
}
2016-10-04 14:10:20 +02:00
// complete the queue
// will stop waiting on the queue or on a latch
_tasks . Complete ( ) ;
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
2016-10-04 14:10:20 +02:00
Thread . Sleep ( 100 ) ; // give time to Complete()
2015-02-06 16:10:34 +01:00
lock ( _locker )
2014-11-12 16:00:17 +11:00
{
2016-10-04 14:10:20 +02:00
// was Complete() enough?
2015-02-06 16:10:34 +01:00
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)
2016-10-04 14:10:20 +02:00
// break latched tasks
// stop processing the queue
2016-10-03 15:08:14 +02:00
_shutdownTokenSource . Cancel ( false ) ; // false is the default
2019-02-04 09:03:54 +01:00
_shutdownTokenSource . Dispose ( ) ;
_shutdownTokenSource = null ;
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
2019-01-08 20:49:21 +01:00
_runningTask ? . Wait ( CancellationToken . None ) ; // 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-04 14:10:20 +02:00
// get the next task
// if it returns null the runner is going down, stop
var bgTask = await GetNextBackgroundTask ( _shutdownToken ) ;
if ( bgTask = = null ) return ;
2016-10-03 15:08:14 +02:00
2016-10-04 14:10:20 +02:00
// set a cancellation source so that the current task can be cancelled
// link from _shutdownToken so that we can use _cancelTokenSource for both
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
_cancelTokenSource = CancellationTokenSource . CreateLinkedTokenSource ( _shutdownToken ) ;
2015-02-06 16:10:34 +01:00
}
2015-04-09 17:49:17 +10:00
2016-10-03 15:08:14 +02:00
try
2015-05-21 16:09:10 +02:00
{
2019-02-04 09:03:54 +01:00
// wait for latch should return the task
// if it returns null it's either that the task has been cancelled
// or the whole runner is going down - in both cases, continue,
// and GetNextBackgroundTask will take care of shutdowns
bgTask = await WaitForLatch ( bgTask , _cancelTokenSource . Token ) ;
if ( bgTask ! = null )
{
// executes & be safe - RunAsync should NOT throw but only raise an event,
// but... just make sure we never ever take everything down
try
{
await RunAsync ( bgTask , _cancelTokenSource . Token ) . ConfigureAwait ( false ) ;
}
catch ( Exception ex )
{
_logger . Error < BackgroundTaskRunner > ( ex , "{LogPrefix} Task runner exception" , _logPrefix ) ;
}
}
2015-05-21 16:09:10 +02:00
}
2019-02-04 09:03:54 +01:00
finally
2015-02-06 16:10:34 +01:00
{
2019-02-04 09:03:54 +01:00
// done
lock ( _locker )
{
// always dispose CancellationTokenSource when you are done using them
// https://lowleveldesign.org/2015/11/30/catch-in-cancellationtokensource/
_cancelTokenSource . Dispose ( ) ;
_cancelTokenSource = null ;
}
2015-02-06 16:10:34 +01:00
}
2016-10-03 15:08:14 +02:00
}
}
2015-02-06 16:10:34 +01:00
2016-10-04 14:10:20 +02:00
// gets the next background task from the buffer
private async Task < T > GetNextBackgroundTask ( CancellationToken token )
2016-10-03 15:08:14 +02:00
{
while ( true )
{
2016-10-04 14:10:20 +02:00
var task = await GetNextBackgroundTask2 ( token ) ;
if ( task ! = null ) return task ;
2015-02-06 16:10:34 +01:00
2016-10-03 15:08:14 +02:00
lock ( _locker )
2015-02-06 16:10:34 +01:00
{
2016-10-04 14:10:20 +02:00
// deal with race condition
2016-10-03 15:08:14 +02:00
if ( _shutdownToken . IsCancellationRequested = = false & & _tasks . Count > 0 ) continue ;
2014-11-12 16:00:17 +11:00
2016-10-04 14:10:20 +02:00
// if we really have nothing to do, stop
2018-08-14 22:36:47 +01:00
_logger . Debug < BackgroundTaskRunner > ( "{LogPrefix} Stopping" , _logPrefix ) ;
2015-02-06 16:10:34 +01:00
2016-10-03 15:08:14 +02:00
if ( _options . PreserveRunningTask = = false )
_runningTask = null ;
_isRunning = false ;
_shutdownToken = CancellationToken . None ;
2015-02-06 16:10:34 +01: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
}
2016-10-04 14:10:20 +02:00
private async Task < T > GetNextBackgroundTask2 ( CancellationToken shutdownToken )
2015-02-06 16:10:34 +01:00
{
2019-01-26 10:52:19 -05:00
// exit if canceling
2016-10-04 14:10:20 +02:00
if ( shutdownToken . IsCancellationRequested )
return null ;
2015-02-06 16:10:34 +01:00
2019-01-26 10:52:19 -05:00
// if KeepAlive is false then don't block, exit if there is
// no task in the buffer - yes, there is a race condition, which
2016-10-04 14:10:20 +02:00
// we'll take care of
if ( _options . KeepAlive = = false & & _tasks . Count = = 0 )
return null ;
2015-02-06 16:10:34 +01:00
2016-10-04 14:10:20 +02:00
try
2016-10-03 15:08:14 +02:00
{
2016-10-04 14:10:20 +02:00
// A Task<TResult> that informs of whether and when more output is available. If, when the
// task completes, its Result is true, more output is available in the source (though another
// consumer of the source may retrieve the data). If it returns false, more output is not
// and will never be available, due to the source completing prior to output being available.
2014-11-12 16:00:17 +11:00
2016-10-04 14:10:20 +02:00
var output = await _tasks . OutputAvailableAsync ( shutdownToken ) ; // block until output or cancelled
if ( output = = false ) return null ;
2016-10-03 15:08:14 +02:00
}
2016-10-04 14:10:20 +02:00
catch ( TaskCanceledException )
2014-11-12 16:00:17 +11:00
{
2016-10-04 14:10:20 +02:00
return null ;
}
try
{
2017-05-12 14:49:44 +02:00
// A task that represents the asynchronous receive operation. When an item value is successfully
2016-10-04 14:10:20 +02:00
// received from the source, the returned task is completed and its Result returns the received
// value. If an item value cannot be retrieved because the source is empty and completed, an
// InvalidOperationException exception is thrown in the returned task.
// the source cannot be empty *and* completed here - we know we have output
return await _tasks . ReceiveAsync ( shutdownToken ) ;
}
catch ( TaskCanceledException )
{
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-04 14:10:20 +02:00
// if bgTask is not a latched background task, or if it is not latched, returns immediately
// else waits for the latch, taking care of completion and shutdown and whatnot
private async Task < T > WaitForLatch ( T bgTask , CancellationToken token )
2015-02-06 16:10:34 +01:00
{
2016-10-04 14:10:20 +02:00
var latched = bgTask as ILatchedBackgroundTask ;
if ( latched = = null | | latched . IsLatched = = false ) return bgTask ;
2019-01-26 10:52:19 -05:00
// support canceling awaiting
2016-10-04 14:10:20 +02:00
// read https://github.com/dotnet/corefx/issues/2704
// read http://stackoverflow.com/questions/27238232/how-can-i-cancel-task-whenall
var tokenTaskSource = new TaskCompletionSource < bool > ( ) ;
token . Register ( s = > ( ( TaskCompletionSource < bool > ) s ) . SetResult ( true ) , tokenTaskSource ) ;
// returns the task that completed
// - latched.Latch completes when the latch releases
// - _tasks.Completion completes when the runner completes
// - tokenTaskSource.Task completes when this task, or the whole runner, is cancelled
var task = await Task . WhenAny ( latched . Latch , _tasks . Completion , tokenTaskSource . Task ) ;
// ok to run now
if ( task = = latched . Latch )
return bgTask ;
// if shutting down, return the task only if it runs on shutdown
if ( _shutdownToken . IsCancellationRequested = = false & & latched . RunsOnShutdown ) return bgTask ;
// else, either it does not run on shutdown or it's been cancelled, dispose
latched . Dispose ( ) ;
return null ;
2014-11-12 16:00:17 +11:00
}
2016-10-04 14:10:20 +02:00
// runs the background task, taking care of shutdown (as far as possible - cannot abort
// a non-async Run for example, so we'll do our best)
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 )
2019-01-26 10:52:19 -05:00
// configure await = false since we don't care about the context, we're on a background thread.
2015-04-08 17:15:21 +10:00
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 )
{
2018-08-14 22:36:47 +01:00
2018-08-17 15:41:58 +01:00
_logger . Error < BackgroundTaskRunner > ( ex , "{LogPrefix} Task has failed" , _logPrefix ) ;
2016-10-04 14:10:20 +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 )
{
2018-08-17 15:41:58 +01:00
_logger . Error < BackgroundTaskRunner > ( ex , "{LogPrefix} {Name} exception occurred" , _logPrefix , name ) ;
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
2016-10-04 14:10:20 +02:00
// dispose it
2015-02-04 15:12:37 +11:00
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>
2019-01-08 20:49:21 +01:00
/// Requests a registered object to un-register.
2015-02-06 16:10:34 +01:00
/// </summary>
2019-01-08 20:49:21 +01:00
/// <param name="immediate">true to indicate the registered object should un-register from the hosting
2015-02-06 16:10:34 +01:00
/// 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>
2019-01-08 20:49:21 +01:00
/// <para>The application manager will call the Stop method to ask a registered object to un-register. During
2015-02-06 16:10:34 +01:00
/// 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 ;
2018-08-14 22:36:47 +01:00
_logger . Info < BackgroundTaskRunner > ( "{LogPrefix} Terminating {Immediate}" , _logPrefix , immediate ? immediate . ToString ( ) : string . Empty ) ;
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.
2018-08-14 22:36:47 +01:00
_logger . Info < BackgroundTaskRunner > ( "{LogPrefix} Waiting for tasks to complete" , _logPrefix ) ;
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.
2019-01-26 10:52:19 -05:00
_logger . Info < BackgroundTaskRunner > ( "{LogPrefix} Canceling tasks" , _logPrefix ) ;
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
2018-08-14 22:36:47 +01:00
_logger . Info < BackgroundTaskRunner > ( "{LogPrefix} Tasks {TaskStatus}, terminated" ,
_logPrefix ,
immediate ? "cancelled" : "completed" ) ;
2016-10-03 15:08:14 +02:00
OnEvent ( Terminated , "Terminated" ) ;
2016-10-05 15:07:47 +02:00
terminatedSource . SetResult ( 0 ) ;
2015-05-19 20:08:52 +02:00
}
2014-11-12 16:00:17 +11:00
}
}