From c5b94620901cc19346b5912f1e9ae09904980378 Mon Sep 17 00:00:00 2001 From: Olly Levett Date: Thu, 12 Jul 2018 19:23:21 +0100 Subject: [PATCH] fix: wait for scheduler guards against exception in early initialisation (#1312) --- .../Platforms/winforms/CommandBindingTests.cs | 0 .../Platforms/winforms/RoutedViewHostTests.cs | 0 .../WaitForDispatcherSchedulerTests.cs | 89 +++++++++++++++++++ .../CreatesCommandBinding.cs | 0 .../windows-common/ReactiveUserControl.cs | 0 src/ReactiveUI/WaitForDispatcherScheduler.cs | 33 +++---- 6 files changed, 106 insertions(+), 16 deletions(-) mode change 100644 => 100755 src/ReactiveUI.Tests/Platforms/winforms/CommandBindingTests.cs mode change 100644 => 100755 src/ReactiveUI.Tests/Platforms/winforms/RoutedViewHostTests.cs create mode 100644 src/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs mode change 100644 => 100755 src/ReactiveUI.Winforms/CreatesCommandBinding.cs mode change 100644 => 100755 src/ReactiveUI/Platforms/windows-common/ReactiveUserControl.cs diff --git a/src/ReactiveUI.Tests/Platforms/winforms/CommandBindingTests.cs b/src/ReactiveUI.Tests/Platforms/winforms/CommandBindingTests.cs old mode 100644 new mode 100755 diff --git a/src/ReactiveUI.Tests/Platforms/winforms/RoutedViewHostTests.cs b/src/ReactiveUI.Tests/Platforms/winforms/RoutedViewHostTests.cs old mode 100644 new mode 100755 diff --git a/src/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs b/src/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs new file mode 100644 index 0000000000..86182980aa --- /dev/null +++ b/src/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs @@ -0,0 +1,89 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MS-PL license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Reactive.Concurrency; +using System.Reactive.Disposables; +using Xunit; + +namespace ReactiveUI.Tests +{ + public class WaitForDispatcherSchedulerTests + { + [Fact] + public void CallSchedulerFactoryOnCreation() + { + var schedulerFactoryCalls = 0; + var schedulerFactory = new Func(() => { schedulerFactoryCalls++; return null; }); + + var sut = new WaitForDispatcherScheduler(schedulerFactory); + + Assert.Equal(1, schedulerFactoryCalls); + } + + [Fact] + public void FactoryThrowsException_ReCallsOnSchedule() + { + var schedulerFactoryCalls = 0; + var schedulerFactory = new Func(() => { + schedulerFactoryCalls++; + throw new InvalidOperationException(); + }); + + var sut = new WaitForDispatcherScheduler(schedulerFactory); + sut.Schedule(() => { }); + + Assert.Equal(2, schedulerFactoryCalls); + } + + [Fact] + public void SuccessfulFactory_UsesCachedScheduler() + { + var schedulerFactoryCalls = 0; + var schedulerFactory = new Func(() => { + schedulerFactoryCalls++; + return CurrentThreadScheduler.Instance; + }); + + var sut = new WaitForDispatcherScheduler(schedulerFactory); + sut.Schedule(() => { }); + + Assert.Equal(1, schedulerFactoryCalls); + } + + [Fact] + public void FactoryThrowsInvalidOperationException_FallsBackToCurrentThread() + { + IScheduler schedulerExecutedOn = null; + var schedulerFactory = new Func(() => { + throw new InvalidOperationException(); + }); + + var sut = new WaitForDispatcherScheduler(schedulerFactory); + sut.Schedule(null, (scheduler, state) => { + schedulerExecutedOn = scheduler; + return Disposable.Empty; + }); + + Assert.Equal(CurrentThreadScheduler.Instance, schedulerExecutedOn); + } + + [Fact] + public void FactoryThrowsArgumentNullException_FallsBackToCurrentThread() + { + IScheduler schedulerExecutedOn = null; + var schedulerFactory = new Func(() => { + throw new ArgumentNullException(); + }); + + var sut = new WaitForDispatcherScheduler(schedulerFactory); + sut.Schedule(null, (scheduler, state) => { + schedulerExecutedOn = scheduler; + return Disposable.Empty; + }); + + Assert.Equal(CurrentThreadScheduler.Instance, schedulerExecutedOn); + } + } +} \ No newline at end of file diff --git a/src/ReactiveUI.Winforms/CreatesCommandBinding.cs b/src/ReactiveUI.Winforms/CreatesCommandBinding.cs old mode 100644 new mode 100755 diff --git a/src/ReactiveUI/Platforms/windows-common/ReactiveUserControl.cs b/src/ReactiveUI/Platforms/windows-common/ReactiveUserControl.cs old mode 100644 new mode 100755 diff --git a/src/ReactiveUI/WaitForDispatcherScheduler.cs b/src/ReactiveUI/WaitForDispatcherScheduler.cs index f6b7b930bc..f60f1e5ce2 100644 --- a/src/ReactiveUI/WaitForDispatcherScheduler.cs +++ b/src/ReactiveUI/WaitForDispatcherScheduler.cs @@ -3,10 +3,7 @@ // See the LICENSE file in the project root for more information. using System; -using System.Collections.Generic; -using System.Linq; using System.Reactive.Concurrency; -using System.Text; namespace ReactiveUI { @@ -18,48 +15,52 @@ namespace ReactiveUI /// public class WaitForDispatcherScheduler : IScheduler { - IScheduler _innerScheduler; - readonly Func _schedulerFactory; + private IScheduler scheduler; + private readonly Func schedulerFactory; public WaitForDispatcherScheduler(Func schedulerFactory) { - _schedulerFactory = schedulerFactory; + this.schedulerFactory = schedulerFactory; // NB: Creating a scheduler will fail on WinRT if we attempt to do // so on a non-UI thread, even if the underlying Dispatcher exists. // We assume (hope?) that WaitForDispatcherScheduler will be created // early enough that this won't be the case. - attemptToCreateScheduler(); + AttemptToCreateScheduler(); } public IDisposable Schedule(TState state, Func action) { - return attemptToCreateScheduler().Schedule(state, action); + return AttemptToCreateScheduler().Schedule(state, action); } public IDisposable Schedule(TState state, TimeSpan dueTime, Func action) { - return attemptToCreateScheduler().Schedule(state, dueTime, action); + return AttemptToCreateScheduler().Schedule(state, dueTime, action); } public IDisposable Schedule(TState state, DateTimeOffset dueTime, Func action) { - return attemptToCreateScheduler().Schedule(state, dueTime, action); + return AttemptToCreateScheduler().Schedule(state, dueTime, action); } - public DateTimeOffset Now { - get { return attemptToCreateScheduler().Now; } + public DateTimeOffset Now + { + get { return AttemptToCreateScheduler().Now; } } - IScheduler attemptToCreateScheduler() + private IScheduler AttemptToCreateScheduler() { - if (_innerScheduler != null) return _innerScheduler; + if (scheduler != null) return scheduler; try { - _innerScheduler = _schedulerFactory(); - return _innerScheduler; + scheduler = schedulerFactory(); + return scheduler; } catch (InvalidOperationException) { // NB: Dispatcher's not ready yet. Keep using CurrentThread return CurrentThreadScheduler.Instance; + } catch (ArgumentNullException) { + // NB: Dispatcher's not ready yet. Keep using CurrentThread + return CurrentThreadScheduler.Instance; } } }