// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Linq; using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Reactive.Linq; using System.Reactive.PlatformServices; using System.Runtime.CompilerServices; using System.Threading; #if NUNIT using NUnit.Framework; using TestClassAttribute = NUnit.Framework.TestFixtureAttribute; using TestMethodAttribute = NUnit.Framework.TestAttribute; using TestInitializeAttribute = NUnit.Framework.SetUpAttribute; #else using Microsoft.VisualStudio.TestTools.UnitTesting; #endif using Microsoft.Reactive.Testing; #if HAS_WINFORMS using System.Windows.Forms; #endif namespace ReactiveTests.Tests { [TestClass] public class SchedulerTest { #region IScheduler [TestMethod] public void Scheduler_ArgumentChecks() { var ms = new MyScheduler(); ReactiveAssert.Throws(() => Scheduler.Schedule(default(IScheduler), a => { })); ReactiveAssert.Throws(() => Scheduler.Schedule(default(IScheduler), () => { })); ReactiveAssert.Throws(() => Scheduler.Schedule(default(IScheduler), 1, (a, s) => { })); ReactiveAssert.Throws(() => Scheduler.Schedule(ms, default(Action))); ReactiveAssert.Throws(() => Scheduler.Schedule(ms, 1, default(Action>))); ReactiveAssert.Throws(() => Scheduler.Schedule(default(IScheduler), DateTimeOffset.Now, a => { })); ReactiveAssert.Throws(() => Scheduler.Schedule(default(IScheduler), DateTimeOffset.Now, () => { })); ReactiveAssert.Throws(() => Scheduler.Schedule(default(IScheduler), 1, DateTimeOffset.Now, (a, s) => { })); ReactiveAssert.Throws(() => Scheduler.Schedule(ms, DateTimeOffset.Now, default(Action>))); ReactiveAssert.Throws(() => Scheduler.Schedule(ms, 1, DateTimeOffset.Now, default(Action>))); ReactiveAssert.Throws(() => Scheduler.Schedule(default(IScheduler), TimeSpan.Zero, a => { })); ReactiveAssert.Throws(() => Scheduler.Schedule(default(IScheduler), TimeSpan.Zero, () => { })); ReactiveAssert.Throws(() => Scheduler.Schedule(default(IScheduler), 1, TimeSpan.Zero, (a, s) => { })); ReactiveAssert.Throws(() => Scheduler.Schedule(ms, TimeSpan.Zero, default(Action>))); ReactiveAssert.Throws(() => Scheduler.Schedule(ms, 1, TimeSpan.Zero, default(Action>))); } [TestMethod] public void Schedulers_ArgumentChecks() { ReactiveAssert.Throws(() => Scheduler.CurrentThread.Schedule(default(Action))); ReactiveAssert.Throws(() => Scheduler.CurrentThread.Schedule(TimeSpan.Zero, default(Action))); ReactiveAssert.Throws(() => Scheduler.CurrentThread.Schedule(DateTimeOffset.MaxValue, default(Action))); #if !NO_WINDOWS_THREADING ReactiveAssert.Throws(() => DispatcherScheduler.Instance.Schedule(default(Action))); ReactiveAssert.Throws(() => DispatcherScheduler.Instance.Schedule(TimeSpan.Zero, default(Action))); ReactiveAssert.Throws(() => DispatcherScheduler.Instance.Schedule(DateTimeOffset.MaxValue, default(Action))); #endif ReactiveAssert.Throws(() => Scheduler.Immediate.Schedule(default(Action))); ReactiveAssert.Throws(() => Scheduler.Immediate.Schedule(TimeSpan.Zero, default(Action))); ReactiveAssert.Throws(() => Scheduler.Immediate.Schedule(DateTimeOffset.MaxValue, default(Action))); ReactiveAssert.Throws(() => NewThreadScheduler.Default.Schedule(default(Action))); ReactiveAssert.Throws(() => NewThreadScheduler.Default.Schedule(TimeSpan.Zero, default(Action))); ReactiveAssert.Throws(() => NewThreadScheduler.Default.Schedule(DateTimeOffset.MaxValue, default(Action))); #if !NO_TPL ReactiveAssert.Throws(() => TaskPoolScheduler.Default.Schedule(default(Action))); ReactiveAssert.Throws(() => TaskPoolScheduler.Default.Schedule(TimeSpan.Zero, default(Action))); ReactiveAssert.Throws(() => TaskPoolScheduler.Default.Schedule(DateTimeOffset.MaxValue, default(Action))); #endif ReactiveAssert.Throws(() => DefaultScheduler.Instance.Schedule(default(Action))); ReactiveAssert.Throws(() => DefaultScheduler.Instance.Schedule(TimeSpan.Zero, default(Action))); ReactiveAssert.Throws(() => DefaultScheduler.Instance.Schedule(DateTimeOffset.MaxValue, default(Action))); ReactiveAssert.Throws(() => DefaultScheduler.Instance.SchedulePeriodic(42, TimeSpan.FromSeconds(1), default(Func))); ReactiveAssert.Throws(() => DefaultScheduler.Instance.SchedulePeriodic(42, TimeSpan.FromSeconds(-1), _ => _)); #if HAS_WINFORMS var lbl = new Label(); ReactiveAssert.Throws(() => new ControlScheduler(lbl).Schedule(default(Action))); ReactiveAssert.Throws(() => new ControlScheduler(lbl).Schedule(TimeSpan.Zero, default(Action))); ReactiveAssert.Throws(() => new ControlScheduler(lbl).Schedule(DateTimeOffset.MaxValue, default(Action))); #endif var ctx = new SynchronizationContext(); ReactiveAssert.Throws(() => new SynchronizationContextScheduler(ctx).Schedule(default(Action))); ReactiveAssert.Throws(() => new SynchronizationContextScheduler(ctx).Schedule(TimeSpan.Zero, default(Action))); ReactiveAssert.Throws(() => new SynchronizationContextScheduler(ctx).Schedule(DateTimeOffset.MaxValue, default(Action))); } [TestMethod] public void Scheduler_ScheduleNonRecursive() { var ms = new MyScheduler(); var res = false; Scheduler.Schedule(ms, a => { res = true; }); Assert.IsTrue(res); } [TestMethod] public void Scheduler_ScheduleRecursive() { var ms = new MyScheduler(); var i = 0; Scheduler.Schedule(ms, a => { if (++i < 10) a(); }); Assert.AreEqual(10, i); } [TestMethod] public void Scheduler_ScheduleWithTimeNonRecursive() { var now = DateTimeOffset.Now; var ms = new MyScheduler(now) { Check = (a, s, t) => { Assert.IsTrue(t == TimeSpan.Zero); } }; var res = false; Scheduler.Schedule(ms, now, a => { res = true; }); Assert.IsTrue(res); Assert.IsTrue(ms.WaitCycles == 0); } [TestMethod] public void Scheduler_ScheduleWithTimeRecursive() { var now = DateTimeOffset.Now; var i = 0; var ms = new MyScheduler(now) { Check = (a, s, t) => { Assert.IsTrue(t == TimeSpan.Zero); } }; Scheduler.Schedule(ms, now, a => { if (++i < 10) a(now); }); Assert.IsTrue(ms.WaitCycles == 0); Assert.AreEqual(10, i); } [TestMethod] public void Scheduler_ScheduleWithTimeSpanNonRecursive() { var now = DateTimeOffset.Now; var ms = new MyScheduler(now) { Check = (a, s, t) => { Assert.IsTrue(t == TimeSpan.Zero); } }; var res = false; Scheduler.Schedule(ms, TimeSpan.Zero, a => { res = true; }); Assert.IsTrue(res); Assert.IsTrue(ms.WaitCycles == 0); } [TestMethod] public void Scheduler_ScheduleWithTimeSpanRecursive() { var now = DateTimeOffset.Now; var ms = new MyScheduler(now) { Check = (a, s, t) => { Assert.IsTrue(t < TimeSpan.FromTicks(10)); } }; var i = 0; Scheduler.Schedule(ms, TimeSpan.Zero, a => { if (++i < 10) a(TimeSpan.FromTicks(i)); }); Assert.IsTrue(ms.WaitCycles == Enumerable.Range(1, 9).Sum()); Assert.AreEqual(10, i); } [TestMethod] public void Scheduler_StateThreading() { var lst = new List(); Scheduler.Immediate.Schedule(0, (i, a) => { lst.Add(i); if (i < 9) a(i + 1); }); Assert.IsTrue(lst.SequenceEqual(Enumerable.Range(0, 10))); } [TestMethod] public void Scheduler_Builtins() { // Default { var e = new ManualResetEvent(false); Scheduler.Default.Schedule(() => e.Set()); e.WaitOne(); } if (!Utils.IsRunningWithPortableLibraryBinaries()) { Scheduler_Builtins_NoPlib(); } } [MethodImpl(MethodImplOptions.NoInlining)] private void Scheduler_Builtins_NoPlib() { #if !PLIB // ThreadPool { var e = new ManualResetEvent(false); Scheduler.ThreadPool.Schedule(() => e.Set()); e.WaitOne(); } #endif #if !NO_THREAD // NewThread { var e = new ManualResetEvent(false); Scheduler.NewThread.Schedule(() => e.Set()); e.WaitOne(); } #endif #if !NO_TPL // TaskPool { var e = new ManualResetEvent(false); Scheduler.TaskPool.Schedule(() => e.Set()); e.WaitOne(); } #endif } #endregion #region ISchedulerLongRunning #if !NO_PERF [TestMethod] public void Scheduler_LongRunning_ArgumentChecking() { ReactiveAssert.Throws(() => Scheduler.ScheduleLongRunning(null, c => { })); ReactiveAssert.Throws(() => Scheduler.ScheduleLongRunning(ThreadPoolScheduler.Instance, default(Action))); } [TestMethod] public void Scheduler_Periodic_ArgumentChecking() { ReactiveAssert.Throws(() => Scheduler.SchedulePeriodic(null, TimeSpan.FromSeconds(1), () => { })); ReactiveAssert.Throws(() => Scheduler.SchedulePeriodic(ThreadPoolScheduler.Instance, TimeSpan.FromSeconds(-1), () => { })); ReactiveAssert.Throws(() => Scheduler.SchedulePeriodic(ThreadPoolScheduler.Instance, TimeSpan.FromSeconds(1), default(Action))); ReactiveAssert.Throws(() => Scheduler.SchedulePeriodic(null, 42, TimeSpan.FromSeconds(1), _ => { })); ReactiveAssert.Throws(() => Scheduler.SchedulePeriodic(ThreadPoolScheduler.Instance, 42, TimeSpan.FromSeconds(-1), _ => { })); ReactiveAssert.Throws(() => Scheduler.SchedulePeriodic(ThreadPoolScheduler.Instance, 42, TimeSpan.FromSeconds(1), default(Action))); ReactiveAssert.Throws(() => Scheduler.SchedulePeriodic(null, 42, TimeSpan.FromSeconds(1), _ => _)); ReactiveAssert.Throws(() => Scheduler.SchedulePeriodic(ThreadPoolScheduler.Instance, 42, TimeSpan.FromSeconds(-1), _ => _)); ReactiveAssert.Throws(() => Scheduler.SchedulePeriodic(ThreadPoolScheduler.Instance, 42, TimeSpan.FromSeconds(1), default(Func))); } [TestMethod] public void Scheduler_Stopwatch_Emulation() { ReactiveAssert.Throws(() => Scheduler.StartStopwatch(null)); } #if !NO_TPL [TestMethod] public void Scheduler_LongRunning1() { var s = TaskPoolScheduler.Default; var x = new ManualResetEvent(false); var e = new ManualResetEvent(false); var d = s.ScheduleLongRunning(42, (state, cancel) => { while (!cancel.IsDisposed) x.Set(); e.Set(); }); x.WaitOne(); d.Dispose(); e.WaitOne(); } [TestMethod] public void Scheduler_LongRunning2() { var s = TaskPoolScheduler.Default; var x = new ManualResetEvent(false); var e = new ManualResetEvent(false); var d = s.ScheduleLongRunning(cancel => { while (!cancel.IsDisposed) x.Set(); e.Set(); }); x.WaitOne(); d.Dispose(); e.WaitOne(); } #endif #endif #endregion #region ISchedulerPeriodic #if !NO_PERF [TestMethod] public void Scheduler_Periodic1() { var n = 0; var e = new ManualResetEvent(false); var d = ThreadPoolScheduler.Instance.SchedulePeriodic(TimeSpan.FromMilliseconds(50), () => { if (n++ == 10) e.Set(); }); e.WaitOne(); d.Dispose(); } [TestMethod] public void Scheduler_Periodic2() { var n = 0; var e = new ManualResetEvent(false); var d = ThreadPoolScheduler.Instance.SchedulePeriodic(42, TimeSpan.FromMilliseconds(50), x => { Assert.AreEqual(42, x); if (n++ == 10) e.Set(); }); e.WaitOne(); d.Dispose(); } #if DESKTOPCLR [TestMethod] public void Scheduler_Periodic_HostLifecycleManagement() { var cur = Utils.GetTestBaseDirectory(); var domain = AppDomain.CreateDomain("HLN", null, new AppDomainSetup { ApplicationBase = cur }); domain.DoCallBack(() => { var pep = PlatformEnlightenmentProvider.Current; try { var hln = new HLN(); PlatformEnlightenmentProvider.Current = new PEP(hln); var s = ThreadPoolScheduler.Instance.DisableOptimizations(typeof(ISchedulerPeriodic)); var n = 0; var e = new ManualResetEvent(false); var d = Observable.Interval(TimeSpan.FromMilliseconds(100), s).Subscribe(_ => { if (n++ == 10) e.Set(); }); hln.OnSuspending(); hln.OnResuming(); Thread.Sleep(250); hln.OnSuspending(); Thread.Sleep(150); hln.OnResuming(); Thread.Sleep(50); hln.OnSuspending(); hln.OnResuming(); e.WaitOne(); d.Dispose(); } finally { PlatformEnlightenmentProvider.Current = pep; } }); } class PEP : IPlatformEnlightenmentProvider { private readonly IPlatformEnlightenmentProvider _old; private readonly IHostLifecycleNotifications _hln; public PEP(HLN hln) { _old = PlatformEnlightenmentProvider.Current; _hln = hln; } public T GetService(params object[] args) where T : class { if (typeof(T) == typeof(IHostLifecycleNotifications)) { return (T)(object)_hln; } return _old.GetService(args); } } class HLN : IHostLifecycleNotifications { public event EventHandler Suspending; public event EventHandler Resuming; public void OnSuspending() { var s = Suspending; if (s != null) s(this, null); } public void OnResuming() { var s = Resuming; if (s != null) s(this, null); } } #endif #endif #endregion #region DisableOptimizations #if !NO_PERF [TestMethod] public void DisableOptimizations_ArgumentChecking() { ReactiveAssert.Throws(() => Scheduler.DisableOptimizations(default(IScheduler))); ReactiveAssert.Throws(() => Scheduler.DisableOptimizations(default(IScheduler), new Type[0])); ReactiveAssert.Throws(() => Scheduler.DisableOptimizations(ThreadPoolScheduler.Instance, default(Type[]))); ReactiveAssert.Throws(() => Scheduler.DisableOptimizations(Scheduler.Default).Schedule(42, default(Func))); ReactiveAssert.Throws(() => Scheduler.DisableOptimizations(Scheduler.Default).Schedule(42, TimeSpan.FromSeconds(1), default(Func))); ReactiveAssert.Throws(() => Scheduler.DisableOptimizations(Scheduler.Default).Schedule(42, DateTimeOffset.Now, default(Func))); } #if !NO_TPL [TestMethod] public void DisableOptimizations1() { var s = TaskPoolScheduler.Default; Assert.IsTrue(s is IServiceProvider); var t = s.DisableOptimizations(); var d = t.Now - s.Now; Assert.IsTrue(d.TotalSeconds < 1); var e1 = new ManualResetEvent(false); t.Schedule(42, (self, state) => { e1.Set(); return Disposable.Empty; }); e1.WaitOne(); var e2 = new ManualResetEvent(false); t.Schedule(42, TimeSpan.FromMilliseconds(1), (self, state) => { e2.Set(); return Disposable.Empty; }); e2.WaitOne(); var e3 = new ManualResetEvent(false); t.Schedule(42, DateTimeOffset.Now.AddMilliseconds(10), (self, state) => { e3.Set(); return Disposable.Empty; }); e3.WaitOne(); } [TestMethod] public void DisableOptimizations2() { var s = TaskPoolScheduler.Default; Assert.IsTrue(s is IServiceProvider); var lr1 = ((IServiceProvider)s).GetService(typeof(ISchedulerLongRunning)); Assert.IsNotNull(lr1); var e1 = new ManualResetEvent(false); s.Schedule(42, (self, state) => { Assert.IsTrue(self is IServiceProvider); var lrr1 = ((IServiceProvider)self).GetService(typeof(ISchedulerLongRunning)); Assert.IsNotNull(lrr1); e1.Set(); return Disposable.Empty; }); e1.WaitOne(); var t = s.DisableOptimizations(); Assert.IsTrue(t is IServiceProvider); var lr2 = ((IServiceProvider)t).GetService(typeof(ISchedulerLongRunning)); Assert.IsNull(lr2); var e2 = new ManualResetEvent(false); t.Schedule(42, (self, state) => { Assert.IsTrue(self is IServiceProvider); var lrr2 = ((IServiceProvider)self).GetService(typeof(ISchedulerLongRunning)); Assert.IsNull(lrr2); e2.Set(); return Disposable.Empty; }); e2.WaitOne(); } [TestMethod] public void DisableOptimizations3() { var s = TaskPoolScheduler.Default; Assert.IsTrue(s is IServiceProvider); var lr1 = ((IServiceProvider)s).GetService(typeof(ISchedulerLongRunning)); Assert.IsNotNull(lr1); var p1 = ((IServiceProvider)s).GetService(typeof(ISchedulerPeriodic)); Assert.IsNotNull(p1); var e1 = new ManualResetEvent(false); s.Schedule(42, (self, state) => { Assert.IsTrue(self is IServiceProvider); var lrr1 = ((IServiceProvider)self).GetService(typeof(ISchedulerLongRunning)); Assert.IsNotNull(lrr1); var pr1 = ((IServiceProvider)self).GetService(typeof(ISchedulerPeriodic)); Assert.IsNotNull(pr1); e1.Set(); return Disposable.Empty; }); e1.WaitOne(); var t = s.DisableOptimizations(typeof(ISchedulerLongRunning)); Assert.IsTrue(t is IServiceProvider); var lr2 = ((IServiceProvider)t).GetService(typeof(ISchedulerLongRunning)); Assert.IsNull(lr2); var p2 = ((IServiceProvider)t).GetService(typeof(ISchedulerPeriodic)); Assert.IsNotNull(p2); var e2 = new ManualResetEvent(false); t.Schedule(42, (self, state) => { Assert.IsTrue(self is IServiceProvider); var lrr2 = ((IServiceProvider)self).GetService(typeof(ISchedulerLongRunning)); Assert.IsNull(lrr2); var pr2 = ((IServiceProvider)self).GetService(typeof(ISchedulerPeriodic)); Assert.IsNotNull(pr2); e2.Set(); return Disposable.Empty; }); e2.WaitOne(); } #endif #endif [TestMethod] public void DisableOptimizations_UnknownService() { var s = new MyScheduler(); Assert.IsFalse(s is IServiceProvider); var d = s.DisableOptimizations(); Assert.IsTrue(d is IServiceProvider); Assert.IsNull(((IServiceProvider)d).GetService(typeof(bool))); } class MyScheduler : IScheduler { public MyScheduler() : this(DateTimeOffset.Now) { } public MyScheduler(DateTimeOffset now) { Now = now; } public DateTimeOffset Now { get; private set; } public IDisposable Schedule(T state, Func action) { return action(this, state); } public Action, object, TimeSpan> Check { get; set; } public long WaitCycles { get; set; } public IDisposable Schedule(T state, TimeSpan dueTime, Func action) { Check(o => action(this, (T)o), state, dueTime); WaitCycles += dueTime.Ticks; return action(this, state); } public IDisposable Schedule(T state, DateTimeOffset dueTime, Func action) { return Schedule(state, dueTime - Now, action); } } #endregion #region Catch [TestMethod] public void Catch_ArgumentChecking() { ReactiveAssert.Throws(() => Scheduler.Catch(default(IScheduler), _ => true)); ReactiveAssert.Throws(() => Scheduler.Catch(Scheduler.Default, default(Func))); ReactiveAssert.Throws(() => Scheduler.Catch(Scheduler.Default, _ => true).Schedule(42, default(Func))); ReactiveAssert.Throws(() => Scheduler.Catch(Scheduler.Default, _ => true).Schedule(42, TimeSpan.FromSeconds(1), default(Func))); ReactiveAssert.Throws(() => Scheduler.Catch(Scheduler.Default, _ => true).Schedule(42, DateTimeOffset.Now, default(Func))); } [TestMethod] public void Catch_Builtin_Swallow_Shallow() { var done = new ManualResetEvent(false); var swallow = Scheduler.Default.Catch(_ => { done.Set(); return true; }); swallow.Schedule(() => { throw new InvalidOperationException(); }); done.WaitOne(); } [TestMethod] public void Catch_Builtin_Swallow_Recursive() { var done = new ManualResetEvent(false); var swallow = Scheduler.Default.Catch(_ => { done.Set(); return true; }); swallow.Schedule(42, (self, state) => { return self.Schedule(() => { throw new InvalidOperationException(); }); }); done.WaitOne(); } [TestMethod] public void Catch_Custom_Unhandled() { var err = default(Exception); var scheduler = new MyExceptionScheduler(ex_ => err = ex_); scheduler.Catch(_ => true).Schedule(() => { throw new InvalidOperationException(); }); Assert.IsNull(err); var ex = new ArgumentException(); scheduler.Catch(_ => true).Schedule(() => { throw ex; }); Assert.AreSame(ex, err); } [TestMethod] public void Catch_Custom_Rethrow() { var err = default(Exception); var scheduler = new MyExceptionScheduler(ex_ => err = ex_); var ex = new InvalidOperationException(); scheduler.Catch(_ => false).Schedule(() => { throw ex; }); Assert.AreSame(ex, err); } [TestMethod] public void Catch_Custom_LongRunning_Caught() { var err = default(Exception); var scheduler = new MyExceptionScheduler(ex_ => err = ex_); var caught = false; var @catch = scheduler.Catch(_ => caught = true); var slr = (ISchedulerLongRunning)((IServiceProvider)@catch).GetService(typeof(ISchedulerLongRunning)); slr.ScheduleLongRunning(cancel => { throw new InvalidOperationException(); }); Assert.IsTrue(caught); Assert.IsNull(err); var ex = new ArgumentException(); slr.ScheduleLongRunning(cancel => { throw ex; }); Assert.AreSame(ex, err); } [TestMethod] public void Catch_Custom_LongRunning_Rethrow() { var err = default(Exception); var scheduler = new MyExceptionScheduler(ex_ => err = ex_); var @catch = scheduler.Catch(_ => false); var slr = (ISchedulerLongRunning)((IServiceProvider)@catch).GetService(typeof(ISchedulerLongRunning)); var done = false; slr.ScheduleLongRunning(cancel => { done = true; }); Assert.IsTrue(done); var ex = new InvalidOperationException(); slr.ScheduleLongRunning(cancel => { throw ex; }); Assert.AreSame(ex, err); } [TestMethod] public void Catch_Custom_Periodic_Regular() { var scheduler = new MyExceptionScheduler(_ => { }); scheduler.PeriodicStopped = new ManualResetEvent(false); var @catch = scheduler.Catch(_ => true); var per = (ISchedulerPeriodic)((IServiceProvider)@catch).GetService(typeof(ISchedulerPeriodic)); var madeProgress = new ManualResetEvent(false); var d = per.SchedulePeriodic(0, TimeSpan.Zero, x => { if (x > 10) madeProgress.Set(); return x + 1; }); madeProgress.WaitOne(); d.Dispose(); scheduler.PeriodicStopped.WaitOne(); } [TestMethod] public void Catch_Custom_Periodic_Uncaught1() { var err = default(Exception); var done = new ManualResetEvent(false); var scheduler = new MyExceptionScheduler(ex_ => { err = ex_; done.Set(); }); scheduler.PeriodicStopped = new ManualResetEvent(false); var @catch = scheduler.Catch(_ => true); var per = (ISchedulerPeriodic)((IServiceProvider)@catch).GetService(typeof(ISchedulerPeriodic)); var ex = new ArgumentException(); per.SchedulePeriodic(42, TimeSpan.Zero, x => { throw ex; }); scheduler.PeriodicStopped.WaitOne(); done.WaitOne(); Assert.AreSame(ex, err); } [TestMethod] public void Catch_Custom_Periodic_Uncaught2() { var err = default(Exception); var done = new ManualResetEvent(false); var scheduler = new MyExceptionScheduler(ex_ => { err = ex_; done.Set(); }); scheduler.PeriodicStopped = new ManualResetEvent(false); var @catch = scheduler.Catch(_ => false); var per = (ISchedulerPeriodic)((IServiceProvider)@catch).GetService(typeof(ISchedulerPeriodic)); var ex = new InvalidOperationException(); per.SchedulePeriodic(42, TimeSpan.Zero, x => { throw ex; }); scheduler.PeriodicStopped.WaitOne(); done.WaitOne(); Assert.AreSame(ex, err); } [TestMethod] public void Catch_Custom_Periodic_Caught() { var err = default(Exception); var scheduler = new MyExceptionScheduler(ex_ => err = ex_); scheduler.PeriodicStopped = new ManualResetEvent(false); var caught = new ManualResetEvent(false); var @catch = scheduler.Catch(_ => { caught.Set(); return true; }); var per = (ISchedulerPeriodic)((IServiceProvider)@catch).GetService(typeof(ISchedulerPeriodic)); per.SchedulePeriodic(42, TimeSpan.Zero, x => { throw new InvalidOperationException(); }); scheduler.PeriodicStopped.WaitOne(); caught.WaitOne(); Assert.IsNull(err); } class MyExceptionScheduler : LocalScheduler, ISchedulerLongRunning, ISchedulerPeriodic { private readonly Action _onError; public MyExceptionScheduler(Action onError) { _onError = onError; } public override IDisposable Schedule(TState state, Func action) { try { return action(this, state); } catch (Exception exception) { _onError(exception); return Disposable.Empty; } } public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { throw new NotImplementedException(); } public IDisposable ScheduleLongRunning(TState state, Action action) { var b = new BooleanDisposable(); try { action(state, b); } catch (Exception exception) { _onError(exception); return Disposable.Empty; } return b; } public ManualResetEvent PeriodicStopped { get; set; } public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) { var b = new BooleanDisposable(); Scheduler.Default.Schedule(() => { try { var s = state; for (int i = 0; true; i++) { if (i > 100 /* mimic delayed cancellation */ && b.IsDisposed) break; s = action(s); } } catch (Exception exception) { _onError(exception); } finally { PeriodicStopped.Set(); } }); return b; } } #endregion #region Services [TestMethod] public void InvalidService_Null() { var s = new MySchedulerWithoutServices(); Assert.IsNull(((IServiceProvider)s).GetService(typeof(IAsyncResult))); } class MySchedulerWithoutServices : LocalScheduler { public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { throw new NotImplementedException(); } } [TestMethod] public void DetectServices_Null_1() { var s = new MyDumbScheduler1(); Assert.IsNull(Scheduler.AsLongRunning(s)); Assert.IsNull(Scheduler.AsPeriodic(s)); Assert.IsNull(Scheduler.AsStopwatchProvider(s)); } class MyDumbScheduler1 : IScheduler { public DateTimeOffset Now { get { throw new NotImplementedException(); } } public IDisposable Schedule(TState state, Func action) { throw new NotImplementedException(); } public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { throw new NotImplementedException(); } public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) { throw new NotImplementedException(); } } [TestMethod] public void DetectServices_Null_2() { var s = new MyDumbScheduler2(new Dictionary()); Assert.IsNull(Scheduler.AsLongRunning(s)); Assert.IsNull(Scheduler.AsPeriodic(s)); Assert.IsNull(Scheduler.AsStopwatchProvider(s)); } [TestMethod] public void DetectServices_Found() { { var x = new MyLongRunning(); var s = new MyDumbScheduler2(new Dictionary { { typeof(ISchedulerLongRunning), x } }); Assert.AreEqual(x, Scheduler.AsLongRunning(s)); } { var x = new MyStopwatchProvider(); var s = new MyDumbScheduler2(new Dictionary { { typeof(IStopwatchProvider), x } }); Assert.AreEqual(x, Scheduler.AsStopwatchProvider(s)); } { var x = new MyPeriodic(); var s = new MyDumbScheduler2(new Dictionary { { typeof(ISchedulerPeriodic), x } }); Assert.AreEqual(x, Scheduler.AsPeriodic(s)); } } class MyDumbScheduler2 : IScheduler, IServiceProvider { private readonly Dictionary _services; public MyDumbScheduler2(Dictionary services) { _services = services; } public DateTimeOffset Now { get { throw new NotImplementedException(); } } public IDisposable Schedule(TState state, Func action) { throw new NotImplementedException(); } public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { throw new NotImplementedException(); } public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) { throw new NotImplementedException(); } public object GetService(Type serviceType) { var res = default(object); if (_services.TryGetValue(serviceType, out res)) return res; return null; } } class MyLongRunning : ISchedulerLongRunning { public IDisposable ScheduleLongRunning(TState state, Action action) { throw new NotImplementedException(); } } class MyStopwatchProvider : IStopwatchProvider { public IStopwatch StartStopwatch() { throw new NotImplementedException(); } } class MyPeriodic : ISchedulerPeriodic { public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) { throw new NotImplementedException(); } } #endregion } }