using System; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Threading; using System.Threading.Tasks; using NUnit.Framework; using Umbraco.Core; using Umbraco.Core.Logging; using Umbraco.Web.Scheduling; namespace Umbraco.Tests.Scheduling { [TestFixture] [Timeout(30000)] public class BackgroundTaskRunnerTests { private ILogger _logger; [TestFixtureSetUp] public void InitializeFixture() { _logger = new DebugDiagnosticsLogger(); } [Test] public async void ShutdownWhenRunningWithWait() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var stopped = false; runner.Stopped += (sender, args) => { stopped = true; }; Assert.IsFalse(runner.IsRunning); // because AutoStart is false runner.Add(new MyTask(5000)); Assert.IsTrue(runner.IsRunning); // is running the task runner.Shutdown(false, true); // -force +wait // all this before we await because +wait Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner Assert.IsFalse(runner.IsRunning); // no more running tasks Assert.IsTrue(stopped); await runner.StoppedAwaitable; // runner stops, within test's timeout } } [Test] public async void ShutdownWhenRunningWithoutWait() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var stopped = false; runner.Stopped += (sender, args) => { stopped = true; }; Assert.IsFalse(runner.IsRunning); // because AutoStart is false runner.Add(new MyTask(5000)); Assert.IsTrue(runner.IsRunning); // is running the task runner.Shutdown(false, false); // -force +wait // all this before we await because -wait Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner Assert.IsTrue(runner.IsRunning); // still running the task Assert.IsFalse(stopped); await runner.StoppedAwaitable; // runner stops, within test's timeout // and then... Assert.IsFalse(runner.IsRunning); // no more running tasks Assert.IsTrue(stopped); } } [Test] public async void ShutdownCompletesTheRunner() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { Assert.IsFalse(runner.IsRunning); // because AutoStart is false // shutdown -force => run all queued tasks runner.Shutdown(false, false); // -force -wait await runner.StoppedAwaitable; // runner stops, within test's timeout Assert.IsFalse(runner.IsRunning); // still not running anything Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner // cannot add tasks to it anymore Assert.IsFalse(runner.TryAdd(new MyTask())); Assert.Throws(() => { runner.Add(new MyTask()); }); } } [Test] public async void ShutdownFlushesTheQueue() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { MyTask t; Assert.IsFalse(runner.IsRunning); // because AutoStart is false runner.Add(new MyTask(5000)); runner.Add(new MyTask()); runner.Add(t = new MyTask()); Assert.IsTrue(runner.IsRunning); // is running tasks // shutdown -force => run all queued tasks runner.Shutdown(false, false); // -force -wait Assert.IsTrue(runner.IsRunning); // is running tasks await runner.StoppedAwaitable; // runner stops, within test's timeout Assert.AreNotEqual(DateTime.MinValue, t.Ended); // t has run } } [Test] public async void ShutdownForceTruncatesTheQueue() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { MyTask t; Assert.IsFalse(runner.IsRunning); // because AutoStart is false runner.Add(new MyTask(5000)); runner.Add(new MyTask()); runner.Add(t = new MyTask()); Assert.IsTrue(runner.IsRunning); // is running tasks // shutdown +force => tries to cancel the current task, ignores queued tasks runner.Shutdown(true, false); // +force -wait Assert.IsTrue(runner.IsRunning); // is running that long task it cannot cancel await runner.StoppedAwaitable; // runner stops, within test's timeout Assert.AreEqual(DateTime.MinValue, t.Ended); // t has *not* run } } [Test] public async void ShutdownThenForce() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { Assert.IsFalse(runner.IsRunning); // because AutoStart is false runner.Add(new MyTask(5000)); runner.Add(new MyTask()); runner.Add(new MyTask()); Assert.IsTrue(runner.IsRunning); // is running tasks // shutdown -force => run all queued tasks runner.Shutdown(false, false); // -force -wait Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner Assert.IsTrue(runner.IsRunning); // still running a task Thread.Sleep(3000); Assert.IsTrue(runner.IsRunning); // still running a task // shutdown +force => tries to cancel the current task, ignores queued tasks runner.Shutdown(true, false); // +force -wait await runner.StoppedAwaitable; // runner stops, within test's timeout } } [Test] public async void HostingStopNonImmediate() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { MyTask t; var stopped = false; runner.Stopped += (sender, args) => { stopped = true; }; var terminating = false; runner.Terminating += (sender, args) => { terminating = true; }; var terminated = false; runner.Terminated += (sender, args) => { terminated = true; }; Assert.IsFalse(runner.IsRunning); // because AutoStart is false runner.Add(new MyTask(5000)); runner.Add(new MyTask()); runner.Add(t = new MyTask()); Assert.IsTrue(runner.IsRunning); // is running the task runner.Stop(false); // -immediate = -force, -wait Assert.IsTrue(terminating); // has raised that event Assert.IsFalse(terminated); // but not terminated yet // all this before we await because -wait Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner Assert.IsTrue(runner.IsRunning); // still running the task await runner.StoppedAwaitable; // runner stops, within test's timeout Assert.IsFalse(runner.IsRunning); Assert.IsTrue(stopped); await runner.TerminatedAwaitable; // runner terminates, within test's timeout Assert.IsTrue(terminated); // has raised that event Assert.AreNotEqual(DateTime.MinValue, t.Ended); // t has run } } [Test] public async void HostingStopImmediate() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { MyTask t; var stopped = false; runner.Stopped += (sender, args) => { stopped = true; }; var terminating = false; runner.Terminating += (sender, args) => { terminating = true; }; var terminated = false; runner.Terminated += (sender, args) => { terminated = true; }; Assert.IsFalse(runner.IsRunning); // because AutoStart is false runner.Add(new MyTask(5000)); runner.Add(new MyTask()); runner.Add(t = new MyTask()); Assert.IsTrue(runner.IsRunning); // is running the task runner.Stop(true); // +immediate = +force, +wait Assert.IsTrue(terminating); // has raised that event Assert.IsTrue(terminated); // and that event Assert.IsTrue(stopped); // and that one // and all this before we await because +wait Assert.IsTrue(runner.IsCompleted); // shutdown completes the runner Assert.IsFalse(runner.IsRunning); // done running await runner.StoppedAwaitable; // runner stops, within test's timeout await runner.TerminatedAwaitable; // runner terminates, within test's timeout Assert.AreEqual(DateTime.MinValue, t.Ended); // t has *not* run } } [Test] public void Create_IsNotRunning() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { Assert.IsFalse(runner.IsRunning); } } [Test] public async void Create_AutoStart_IsRunning() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { AutoStart = true, KeepAlive = true // else stops! }, _logger)) { Assert.IsTrue(runner.IsRunning); // because AutoStart is true runner.Stop(false); // keepalive = must be stopped await runner.StoppedAwaitable; // runner stops, within test's timeout } } [Test] public void Create_AutoStartAndKeepAlive_IsRunning() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { AutoStart = true, KeepAlive = true }, _logger)) { Assert.IsTrue(runner.IsRunning); // because AutoStart is true Thread.Sleep(800); // for long Assert.IsTrue(runner.IsRunning); // dispose will stop it } } [Test] public async void Dispose_IsRunning() { BackgroundTaskRunner runner; using (runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { AutoStart = true, KeepAlive = true }, _logger)) { Assert.IsTrue(runner.IsRunning); // dispose will stop it } await runner.StoppedAwaitable; // runner stops, within test's timeout //await runner.TerminatedAwaitable; // NO! see note below Assert.Throws(() => runner.Add(new MyTask())); // but do NOT await on TerminatedAwaitable - disposing just shuts the runner down // so that we don't have a runaway task in tests, etc - but it does NOT terminate // the runner - it really is NOT a nice way to end a runner - it's there for tests } [Test] public void Startup_KeepAlive_IsRunning() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true }, _logger)) { Assert.IsFalse(runner.IsRunning); runner.StartUp(); Assert.IsTrue(runner.IsRunning); // dispose will stop it } } [Test] public async void Create_AddTask_IsRunning() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var waitHandle = new ManualResetEvent(false); runner.TaskCompleted += (sender, args) => { waitHandle.Set(); }; runner.Add(new MyTask()); Assert.IsTrue(runner.IsRunning); waitHandle.WaitOne(); await runner.StoppedAwaitable; //since we are not being kept alive, it will quit Assert.IsFalse(runner.IsRunning); } } [Test] public void Create_KeepAliveAndAddTask_IsRunning() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true }, _logger)) { var waitHandle = new ManualResetEvent(false); runner.TaskCompleted += (sender, args) => { Assert.IsTrue(sender.IsRunning); waitHandle.Set(); }; runner.Add(new MyTask()); waitHandle.WaitOne(); Thread.Sleep(1000); // we are waiting a second just to prove that it's still running and hasn't been shut off Assert.IsTrue(runner.IsRunning); } } [Test] public async void WaitOnRunner_OneTask() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var task = new MyTask(); Assert.IsTrue(task.Ended == default(DateTime)); runner.Add(task); await runner.CurrentThreadingTask; // wait for the Task operation to complete Assert.IsTrue(task.Ended != default(DateTime)); // task is done await runner.StoppedAwaitable; // wait for the entire runner operation to complete } } [Test] public async void WaitOnRunner_Tasks() { var tasks = new List(); for (var i = 0; i < 10; i++) tasks.Add(new MyTask()); using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = false, LongRunning = true, PreserveRunningTask = true }, _logger)) { tasks.ForEach(runner.Add); await runner.StoppedAwaitable; // wait for the entire runner operation to complete // check that tasks are done Assert.IsTrue(tasks.All(x => x.Ended != default(DateTime))); Assert.AreEqual(TaskStatus.RanToCompletion, runner.CurrentThreadingTask.Status); Assert.IsFalse(runner.IsRunning); Assert.IsFalse(runner.IsDisposed); } } [Test] public async void WaitOnTask() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var task = new MyTask(); var waitHandle = new ManualResetEvent(false); runner.TaskCompleted += (sender, t) => waitHandle.Set(); Assert.IsTrue(task.Ended == default(DateTime)); runner.Add(task); waitHandle.WaitOne(); // wait 'til task is done Assert.IsTrue(task.Ended != default(DateTime)); // task is done await runner.StoppedAwaitable; // wait for the entire runner operation to complete } } [Test] public async void WaitOnTasks() { var tasks = new Dictionary(); for (var i = 0; i < 10; i++) tasks.Add(new MyTask(), new ManualResetEvent(false)); using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { runner.TaskCompleted += (sender, task) => tasks[task.Task].Set(); foreach (var t in tasks) runner.Add(t.Key); // wait 'til tasks are done, check that tasks are done WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray()); Assert.IsTrue(tasks.All(x => x.Key.Ended != default(DateTime))); await runner.StoppedAwaitable; // wait for the entire runner operation to complete } } [Test] public void Tasks_Can_Keep_Being_Added_And_Will_Execute() { Func> getTasks = () => { var newTasks = new Dictionary(); for (var i = 0; i < 10; i++) { newTasks.Add(new MyTask(), new ManualResetEvent(false)); } return newTasks; }; IDictionary tasks = getTasks(); BackgroundTaskRunner tManager; using (tManager = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { LongRunning = true, KeepAlive = true }, _logger)) { tManager.TaskCompleted += (sender, task) => tasks[task.Task].Set(); //execute first batch tasks.ForEach(t => tManager.Add(t.Key)); //wait for all ITasks to complete WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray()); foreach (var task in tasks) { Assert.IsTrue(task.Key.Ended != default(DateTime)); } //execute another batch after a bit Thread.Sleep(2000); tasks = getTasks(); tasks.ForEach(t => tManager.Add(t.Key)); //wait for all ITasks to complete WaitHandle.WaitAll(tasks.Values.Select(x => (WaitHandle)x).ToArray()); foreach (var task in tasks) { Assert.IsTrue(task.Key.Ended != default(DateTime)); } } } [Test] public async void Non_Persistent_Runner_Will_Start_New_Threads_When_Required() { Func> getTasks = () => { var newTasks = new List(); for (var i = 0; i < 10; i++) { newTasks.Add(new MyTask()); } return newTasks; }; List tasks = getTasks(); using (var tManager = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { LongRunning = true, PreserveRunningTask = true }, _logger)) { tasks.ForEach(tManager.Add); //wait till the thread is done await tManager.CurrentThreadingTask; Assert.AreEqual(TaskStatus.RanToCompletion, tManager.CurrentThreadingTask.Status); Assert.IsFalse(tManager.IsRunning); Assert.IsFalse(tManager.IsDisposed); foreach (var task in tasks) { Assert.IsTrue(task.Ended != default(DateTime)); } //create more tasks tasks = getTasks(); //add more tasks tasks.ForEach(tManager.Add); //wait till the thread is done await tManager.CurrentThreadingTask; foreach (var task in tasks) { Assert.IsTrue(task.Ended != default(DateTime)); } Assert.AreEqual(TaskStatus.RanToCompletion, tManager.CurrentThreadingTask.Status); Assert.IsFalse(tManager.IsRunning); Assert.IsFalse(tManager.IsDisposed); } } [Test] public void RecurringTaskTest() { var runCount = 0; var waitHandle = new ManualResetEvent(false); using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { runner.TaskCompleted += (sender, args) => { runCount++; if (runCount > 3) waitHandle.Set(); }; var task = new MyRecurringTask(runner, 200, 500); runner.Add(task); Assert.IsTrue(runner.IsRunning); // waiting on delay Assert.AreEqual(0, runCount); waitHandle.WaitOne(); Assert.GreaterOrEqual(runCount, 4); // stops recurring runner.Shutdown(false, true); // check that task has been disposed (timer has been killed, etc) Assert.IsTrue(task.Disposed); } } [Test] public async void LatchedTaskRuns() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var task = new MyLatchedTask(200, false); runner.Add(task); Assert.IsTrue(runner.IsRunning); Thread.Sleep(1000); Assert.IsTrue(runner.IsRunning); // still waiting for the task to release Assert.IsFalse(task.HasRun); task.Release(); await runner.CurrentThreadingTask; // wait for current task to complete Assert.IsTrue(task.HasRun); await runner.StoppedAwaitable; // wait for the entire runner operation to complete } } [Test] public async void LatchedTaskStops() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var task = new MyLatchedTask(200, true); runner.Add(task); Assert.IsTrue(runner.IsRunning); Thread.Sleep(5000); Assert.IsTrue(runner.IsRunning); // still waiting for the task to release Assert.IsFalse(task.HasRun); runner.Shutdown(false, false); await runner.StoppedAwaitable; // wait for the entire runner operation to complete Assert.IsTrue(task.HasRun); } } [Test] public void LatchedRecurring() { var runCount = 0; var waitHandle = new ManualResetEvent(false); using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { runner.TaskCompleted += (sender, args) => { runCount++; if (runCount > 3) waitHandle.Set(); }; var task = new MyDelayedRecurringTask(runner, 2000, 1000); runner.Add(task); Assert.IsTrue(runner.IsRunning); // waiting on delay Assert.AreEqual(0, runCount); waitHandle.WaitOne(); Assert.GreaterOrEqual(runCount, 4); Assert.IsTrue(task.HasRun); // stops recurring runner.Shutdown(false, false); } } [Test] public async void FailingTaskSync() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var exceptions = new ConcurrentQueue(); runner.TaskError += (sender, args) => exceptions.Enqueue(args.Exception); var task = new MyFailingTask(false, true, false); // -async, +running, -disposing runner.Add(task); Assert.IsTrue(runner.IsRunning); await runner.StoppedAwaitable; // wait for the entire runner operation to complete Assert.AreEqual(1, exceptions.Count); // traced and reported } } [Test] public async void FailingTaskDisposing() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var exceptions = new ConcurrentQueue(); runner.TaskError += (sender, args) => exceptions.Enqueue(args.Exception); var task = new MyFailingTask(false, false, true); // -async, -running, +disposing runner.Add(task); Assert.IsTrue(runner.IsRunning); await runner.StoppedAwaitable; // wait for the entire runner operation to complete Assert.AreEqual(1, exceptions.Count); // traced and reported } } [Test] public async void FailingTaskAsync() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var exceptions = new ConcurrentQueue(); runner.TaskError += (sender, args) => exceptions.Enqueue(args.Exception); var task = new MyFailingTask(true, true, false); // +async, +running, -disposing runner.Add(task); Assert.IsTrue(runner.IsRunning); await runner.StoppedAwaitable; // wait for the entire runner operation to complete Assert.AreEqual(1, exceptions.Count); // traced and reported } } [Test] public async void FailingTaskDisposingAsync() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var exceptions = new ConcurrentQueue(); runner.TaskError += (sender, args) => exceptions.Enqueue(args.Exception); var task = new MyFailingTask(false, false, true); // -async, -running, +disposing runner.Add(task); Assert.IsTrue(runner.IsRunning); await runner.StoppedAwaitable; // wait for the entire runner operation to complete Assert.AreEqual(1, exceptions.Count); // traced and reported } } [Test] public async void CancelAsyncTask() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var task = new MyAsyncTask(4000); runner.Add(task); Assert.IsTrue(runner.IsRunning); await Task.Delay(1000); // ensure the task *has* started else cannot cancel runner.CancelCurrentBackgroundTask(); await runner.StoppedAwaitable; // wait for the entire runner operation to complete Assert.AreEqual(default(DateTime), task.Ended); } } [Test] public async void CancelLatchedTask() { using (var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions(), _logger)) { var task = new MyLatchedTask(4000, false); runner.Add(task); Assert.IsTrue(runner.IsRunning); await Task.Delay(1000); // ensure the task *has* started else cannot cancel runner.CancelCurrentBackgroundTask(); await runner.StoppedAwaitable; // wait for the entire runner operation to complete Assert.IsFalse(task.HasRun); } } private class MyFailingTask : IBackgroundTask { private readonly bool _isAsync; private readonly bool _running; private readonly bool _disposing; public MyFailingTask(bool isAsync, bool running, bool disposing) { _isAsync = isAsync; _running = running; _disposing = disposing; } public void Run() { Thread.Sleep(1000); if (_running) throw new Exception("Task has thrown."); } public async Task RunAsync(CancellationToken token) { await Task.Delay(1000, token); if (_running) throw new Exception("Task has thrown."); } public bool IsAsync { get { return _isAsync; } } public void Dispose() { if (_disposing) throw new Exception("Task has thrown."); } } private class MyDelayedRecurringTask : RecurringTaskBase { public bool HasRun { get; private set; } public MyDelayedRecurringTask(IBackgroundTaskRunner runner, int delayMilliseconds, int periodMilliseconds) : base(runner, delayMilliseconds, periodMilliseconds) { } public override bool IsAsync { get { return false; } } public override bool PerformRun() { HasRun = true; return true; // repeat } public override Task PerformRunAsync(CancellationToken token) { throw new NotImplementedException(); } public override bool RunsOnShutdown { get { return true; } } } private class MyLatchedTask : ILatchedBackgroundTask { private readonly int _runMilliseconds; private readonly TaskCompletionSource _latch; public bool HasRun { get; private set; } public MyLatchedTask(int runMilliseconds, bool runsOnShutdown) { _runMilliseconds = runMilliseconds; _latch = new TaskCompletionSource(); RunsOnShutdown = runsOnShutdown; } public Task Latch { get { return _latch.Task; } } public bool IsLatched { get { return _latch.Task.IsCompleted == false; } } public bool RunsOnShutdown { get; private set; } public void Run() { Thread.Sleep(_runMilliseconds); HasRun = true; } public void Release() { _latch.SetResult(true); } public Task RunAsync(CancellationToken token) { throw new NotImplementedException(); } public bool IsAsync { get { return false; } } public void Dispose() { } } private class MyRecurringTask : RecurringTaskBase { private readonly int _runMilliseconds; public MyRecurringTask(IBackgroundTaskRunner runner, int runMilliseconds, int periodMilliseconds) : base(runner, 0, periodMilliseconds) { _runMilliseconds = runMilliseconds; } public override bool PerformRun() { Thread.Sleep(_runMilliseconds); return true; // repeat } public override Task PerformRunAsync(CancellationToken token) { throw new NotImplementedException(); } public override bool IsAsync { get { return false; } } public override bool RunsOnShutdown { get { return false; } } } private class MyTask : BaseTask { private readonly int _milliseconds; public MyTask() : this(500) { } public MyTask(int milliseconds) { _milliseconds = milliseconds; } public override void PerformRun() { Thread.Sleep(_milliseconds); } } private class MyAsyncTask : BaseTask { private readonly int _milliseconds; public MyAsyncTask() : this(500) { } public MyAsyncTask(int milliseconds) { _milliseconds = milliseconds; } public override void PerformRun() { throw new NotImplementedException(); } public override async Task RunAsync(CancellationToken token) { await Task.Delay(_milliseconds, token); Ended = DateTime.Now; } public override bool IsAsync { get { return true; } } } [Test] public void SourceTaskTest() { var runner = new BackgroundTaskRunner(new BackgroundTaskRunnerOptions { KeepAlive = true, LongRunning = true }, _logger); var task = new SourceTask(); runner.Add(task); Assert.IsTrue(runner.IsRunning); Console.WriteLine("completing"); task.Complete(); // in Deploy this does not return ffs - no point until I cannot repro Console.WriteLine("completed"); Console.WriteLine("done"); } private class SourceTask : IBackgroundTask { private readonly SemaphoreSlim _timeout = new SemaphoreSlim(0, 1); private readonly TaskCompletionSource _source = new TaskCompletionSource(); public void Complete() { _source.SetResult(null); } public void Dispose() { } public void Run() { throw new NotImplementedException(); } public async Task RunAsync(CancellationToken token) { Console.WriteLine("boom"); var timeout = _timeout.WaitAsync(token); var task = WorkItemRunAsync(); var anyTask = await Task.WhenAny(task, timeout).ConfigureAwait(false); } private async Task WorkItemRunAsync() { await _source.Task.ConfigureAwait(false); } public bool IsAsync { get { return true; } } } public abstract class BaseTask : IBackgroundTask { public bool WasCancelled { get; set; } public Guid UniqueId { get; protected set; } public abstract void PerformRun(); public void Run() { PerformRun(); Ended = DateTime.Now; } public virtual Task RunAsync(CancellationToken token) { throw new NotImplementedException(); //return Task.Delay(500); } public virtual bool IsAsync { get { return false; } } public virtual void Cancel() { WasCancelled = true; } public DateTime Queued { get; set; } public DateTime Started { get; set; } public DateTime Ended { get; set; } public virtual void Dispose() { } } } }