Skip to content

Commit

Permalink
fix: wait for scheduler guards against exception in early initialisat…
Browse files Browse the repository at this point in the history
…ion (#1312)
  • Loading branch information
olevett authored Jul 12, 2018
1 parent e3eb8ef commit c5b9462
Show file tree
Hide file tree
Showing 6 changed files with 106 additions and 16 deletions.
Empty file modified src/ReactiveUI.Tests/Platforms/winforms/CommandBindingTests.cs
100644 → 100755
Empty file.
Empty file modified src/ReactiveUI.Tests/Platforms/winforms/RoutedViewHostTests.cs
100644 → 100755
Empty file.
89 changes: 89 additions & 0 deletions src/ReactiveUI.Tests/WaitForDispatcherSchedulerTests.cs
Original file line number Diff line number Diff line change
@@ -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<IScheduler>(() => { schedulerFactoryCalls++; return null; });

var sut = new WaitForDispatcherScheduler(schedulerFactory);

Assert.Equal(1, schedulerFactoryCalls);
}

[Fact]
public void FactoryThrowsException_ReCallsOnSchedule()
{
var schedulerFactoryCalls = 0;
var schedulerFactory = new Func<IScheduler>(() => {
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<IScheduler>(() => {
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<IScheduler>(() => {
throw new InvalidOperationException();
});

var sut = new WaitForDispatcherScheduler(schedulerFactory);
sut.Schedule<object>(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<IScheduler>(() => {
throw new ArgumentNullException();
});

var sut = new WaitForDispatcherScheduler(schedulerFactory);
sut.Schedule<object>(null, (scheduler, state) => {
schedulerExecutedOn = scheduler;
return Disposable.Empty;
});

Assert.Equal(CurrentThreadScheduler.Instance, schedulerExecutedOn);
}
}
}
Empty file modified src/ReactiveUI.Winforms/CreatesCommandBinding.cs
100644 → 100755
Empty file.
Empty file modified src/ReactiveUI/Platforms/windows-common/ReactiveUserControl.cs
100644 → 100755
Empty file.
33 changes: 17 additions & 16 deletions src/ReactiveUI/WaitForDispatcherScheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
{
Expand All @@ -18,48 +15,52 @@ namespace ReactiveUI
/// </summary>
public class WaitForDispatcherScheduler : IScheduler
{
IScheduler _innerScheduler;
readonly Func<IScheduler> _schedulerFactory;
private IScheduler scheduler;
private readonly Func<IScheduler> schedulerFactory;

public WaitForDispatcherScheduler(Func<IScheduler> 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>(TState state, Func<IScheduler, TState, IDisposable> action)
{
return attemptToCreateScheduler().Schedule(state, action);
return AttemptToCreateScheduler().Schedule(state, action);
}

public IDisposable Schedule<TState>(TState state, TimeSpan dueTime, Func<IScheduler, TState, IDisposable> action)
{
return attemptToCreateScheduler().Schedule(state, dueTime, action);
return AttemptToCreateScheduler().Schedule(state, dueTime, action);
}

public IDisposable Schedule<TState>(TState state, DateTimeOffset dueTime, Func<IScheduler, TState, IDisposable> 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;
}
}
}
Expand Down

0 comments on commit c5b9462

Please sign in to comment.