// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information. #if WINDOWS using System.Reactive.Concurrency; using System.Reactive.Disposables; using System.Runtime.ExceptionServices; using System.Threading; using Windows.UI.Core; using Windows.UI.Xaml; namespace System.Reactive.Concurrency { /// /// Represents an object that schedules units of work on a Windows.UI.Core.CoreDispatcher. /// /// /// This scheduler type is typically used indirectly through the and methods that use the current Dispatcher. /// [CLSCompliant(false)] public sealed class CoreDispatcherScheduler : LocalScheduler, ISchedulerPeriodic { private readonly CoreDispatcher _dispatcher; private readonly CoreDispatcherPriority _priority; /// /// Constructs a CoreDispatcherScheduler that schedules units of work on the given Windows.UI.Core.CoreDispatcher. /// /// Dispatcher to schedule work on. /// is null. public CoreDispatcherScheduler(CoreDispatcher dispatcher) { if (dispatcher == null) throw new ArgumentNullException("dispatcher"); _dispatcher = dispatcher; _priority = CoreDispatcherPriority.Normal; } /// /// Constructs a CoreDispatcherScheduler that schedules units of work on the given Windows.UI.Core.CoreDispatcher with the given priority. /// /// Dispatcher to schedule work on. /// Priority for scheduled units of work. /// is null. public CoreDispatcherScheduler(CoreDispatcher dispatcher, CoreDispatcherPriority priority) { if (dispatcher == null) throw new ArgumentNullException("dispatcher"); _dispatcher = dispatcher; _priority = priority; } /// /// Gets the scheduler that schedules work on the Windows.UI.Core.CoreDispatcher associated with the current Window. /// public static CoreDispatcherScheduler Current { get { var window = Window.Current; if (window == null) throw new InvalidOperationException(Strings_WindowsThreading.NO_WINDOW_CURRENT); return new CoreDispatcherScheduler(window.Dispatcher); } } /// /// Gets the Windows.UI.Core.CoreDispatcher associated with the CoreDispatcherScheduler. /// public CoreDispatcher Dispatcher { get { return _dispatcher; } } /// /// Gets the priority at which work is scheduled. /// public CoreDispatcherPriority Priority { get { return _priority; } } /// /// Schedules an action to be executed on the dispatcher. /// /// The type of the state passed to the scheduled action. /// State passed to the action to be executed. /// Action to be executed. /// The disposable object used to cancel the scheduled action (best effort). /// is null. public override IDisposable Schedule(TState state, Func action) { if (action == null) throw new ArgumentNullException("action"); var d = new SingleAssignmentDisposable(); var res = _dispatcher.RunAsync(_priority, () => { if (!d.IsDisposed) { try { d.Disposable = action(this, state); } catch (Exception ex) { // // Work-around for the behavior of throwing from RunAsync not propagating // the exception to the Application.UnhandledException event (as of W8RP) // as our users have come to expect from previous XAML stacks using Rx. // // If we wouldn't do this, there'd be an observable behavioral difference // between scheduling with TimeSpan.Zero or using this overload. // // For scheduler implementation guidance rules, see TaskPoolScheduler.cs // in System.Reactive.PlatformServices\Reactive\Concurrency. // var timer = new DispatcherTimer(); timer.Interval = TimeSpan.Zero; timer.Tick += (o, e) => { timer.Stop(); ExceptionDispatchInfo.Capture(ex).Throw(); }; timer.Start(); } } }); return new CompositeDisposable( d, Disposable.Create(res.Cancel) ); } /// /// Schedules an action to be executed after dueTime on the dispatcher, using a Windows.UI.Xaml.DispatcherTimer object. /// /// The type of the state passed to the scheduled action. /// State passed to the action to be executed. /// Action to be executed. /// Relative time after which to execute the action. /// The disposable object used to cancel the scheduled action (best effort). /// is null. public override IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { if (action == null) throw new ArgumentNullException("action"); var dt = Scheduler.Normalize(dueTime); if (dt.Ticks == 0) return Schedule(state, action); var d = new MultipleAssignmentDisposable(); var timer = new DispatcherTimer(); timer.Tick += (o, e) => { var t = Interlocked.Exchange(ref timer, null); if (t != null) { try { d.Disposable = action(this, state); } finally { t.Stop(); action = null; } } }; timer.Interval = dt; timer.Start(); d.Disposable = Disposable.Create(() => { var t = Interlocked.Exchange(ref timer, null); if (t != null) { t.Stop(); action = (_, __) => Disposable.Empty; } }); return d; } /// /// Schedules a periodic piece of work on the dispatcher, using a Windows.UI.Xaml.DispatcherTimer object. /// /// The type of the state passed to the scheduled action. /// Initial state passed to the action upon the first iteration. /// Period for running the work periodically. /// Action to be executed, potentially updating the state. /// The disposable object used to cancel the scheduled recurring action (best effort). /// is null. /// is less than TimeSpan.Zero. public IDisposable SchedulePeriodic(TState state, TimeSpan period, Func action) { // // According to MSDN documentation, the default is TimeSpan.Zero, so that's definitely valid. // Empirical observation - negative values seem to be normalized to TimeSpan.Zero, but let's not go there. // if (period < TimeSpan.Zero) throw new ArgumentOutOfRangeException("period"); if (action == null) throw new ArgumentNullException("action"); var timer = new DispatcherTimer(); var state1 = state; timer.Tick += (o, e) => { state1 = action(state1); }; timer.Interval = period; timer.Start(); return Disposable.Create(() => { var t = Interlocked.Exchange(ref timer, null); if (t != null) { t.Stop(); action = _ => _; } }); } } } #endif