Skip to content

Commit 06459fc

Browse files
authored
Feature ReactiveCommand.CreateRunInBackground (#3501)
1 parent 42929d4 commit 06459fc

12 files changed

+348
-242
lines changed

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet6_0.verified.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ namespace ReactiveUI
6666
}
6767
public class CombinedReactiveCommand<TParam, TResult> : ReactiveUI.ReactiveCommandBase<TParam, System.Collections.Generic.IList<TResult>>
6868
{
69-
protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable<ReactiveUI.ReactiveCommandBase<TParam, TResult>> childCommands, System.IObservable<bool> canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
69+
protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable<ReactiveUI.ReactiveCommandBase<TParam, TResult>> childCommands, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
7070
public override System.IObservable<bool> CanExecute { get; }
7171
public override System.IObservable<bool> IsExecuting { get; }
7272
public override System.IObservable<System.Exception> ThrownExceptions { get; }
@@ -638,6 +638,10 @@ namespace ReactiveUI
638638
public static ReactiveUI.ReactiveCommand<TParam, System.Reactive.Unit> CreateFromTask<TParam>(System.Func<TParam, System.Threading.CancellationToken, System.Threading.Tasks.Task> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
639639
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateFromTask<TParam, TResult>(System.Func<TParam, System.Threading.Tasks.Task<TResult>> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
640640
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateFromTask<TParam, TResult>(System.Func<TParam, System.Threading.CancellationToken, System.Threading.Tasks.Task<TResult>> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
641+
public static ReactiveUI.ReactiveCommand<System.Reactive.Unit, System.Reactive.Unit> CreateRunInBackground(System.Action execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
642+
public static ReactiveUI.ReactiveCommand<TParam, System.Reactive.Unit> CreateRunInBackground<TParam>(System.Action<TParam> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
643+
public static ReactiveUI.ReactiveCommand<System.Reactive.Unit, TResult> CreateRunInBackground<TResult>(System.Func<TResult> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
644+
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateRunInBackground<TParam, TResult>(System.Func<TParam, TResult> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
641645
}
642646
public abstract class ReactiveCommandBase<TParam, TResult> : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveCommand, System.IDisposable, System.IObservable<TResult>, System.Windows.Input.ICommand
643647
{
@@ -665,7 +669,7 @@ namespace ReactiveUI
665669
}
666670
public class ReactiveCommand<TParam, TResult> : ReactiveUI.ReactiveCommandBase<TParam, TResult>
667671
{
668-
protected ReactiveCommand(System.Func<TParam, System.IObservable<TResult>> execute, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
672+
protected ReactiveCommand(System.Func<TParam, System.IObservable<TResult>> execute, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler) { }
669673
public override System.IObservable<bool> CanExecute { get; }
670674
public override System.IObservable<bool> IsExecuting { get; }
671675
public override System.IObservable<System.Exception> ThrownExceptions { get; }

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.Net4_7.verified.txt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ namespace ReactiveUI
6666
}
6767
public class CombinedReactiveCommand<TParam, TResult> : ReactiveUI.ReactiveCommandBase<TParam, System.Collections.Generic.IList<TResult>>
6868
{
69-
protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable<ReactiveUI.ReactiveCommandBase<TParam, TResult>> childCommands, System.IObservable<bool> canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
69+
protected CombinedReactiveCommand(System.Collections.Generic.IEnumerable<ReactiveUI.ReactiveCommandBase<TParam, TResult>> childCommands, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
7070
public override System.IObservable<bool> CanExecute { get; }
7171
public override System.IObservable<bool> IsExecuting { get; }
7272
public override System.IObservable<System.Exception> ThrownExceptions { get; }
@@ -645,6 +645,10 @@ namespace ReactiveUI
645645
public static ReactiveUI.ReactiveCommand<TParam, System.Reactive.Unit> CreateFromTask<TParam>(System.Func<TParam, System.Threading.CancellationToken, System.Threading.Tasks.Task> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
646646
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateFromTask<TParam, TResult>(System.Func<TParam, System.Threading.Tasks.Task<TResult>> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
647647
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateFromTask<TParam, TResult>(System.Func<TParam, System.Threading.CancellationToken, System.Threading.Tasks.Task<TResult>> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
648+
public static ReactiveUI.ReactiveCommand<System.Reactive.Unit, System.Reactive.Unit> CreateRunInBackground(System.Action execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
649+
public static ReactiveUI.ReactiveCommand<TParam, System.Reactive.Unit> CreateRunInBackground<TParam>(System.Action<TParam> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
650+
public static ReactiveUI.ReactiveCommand<System.Reactive.Unit, TResult> CreateRunInBackground<TResult>(System.Func<TResult> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
651+
public static ReactiveUI.ReactiveCommand<TParam, TResult> CreateRunInBackground<TParam, TResult>(System.Func<TParam, TResult> execute, System.IObservable<bool>? canExecute = null, System.Reactive.Concurrency.IScheduler? backgroundScheduler = null, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
648652
}
649653
public abstract class ReactiveCommandBase<TParam, TResult> : ReactiveUI.IHandleObservableErrors, ReactiveUI.IReactiveCommand, System.IDisposable, System.IObservable<TResult>, System.Windows.Input.ICommand
650654
{
@@ -672,7 +676,7 @@ namespace ReactiveUI
672676
}
673677
public class ReactiveCommand<TParam, TResult> : ReactiveUI.ReactiveCommandBase<TParam, TResult>
674678
{
675-
protected ReactiveCommand(System.Func<TParam, System.IObservable<TResult>> execute, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler = null) { }
679+
protected ReactiveCommand(System.Func<TParam, System.IObservable<TResult>> execute, System.IObservable<bool>? canExecute, System.Reactive.Concurrency.IScheduler? outputScheduler) { }
676680
public override System.IObservable<bool> CanExecute { get; }
677681
public override System.IObservable<bool> IsExecuting { get; }
678682
public override System.IObservable<System.Exception> ThrownExceptions { get; }

src/ReactiveUI.Tests/Commands/CombinedReactiveCommandTest.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Reactive.Concurrency;
99
using System.Reactive.Linq;
1010
using System.Reactive.Subjects;
11+
using System.Threading.Tasks;
1112
using DynamicData;
1213
using Microsoft.Reactive.Testing;
1314
using ReactiveUI.Testing;
@@ -104,7 +105,6 @@ public void ExceptionsAreDeliveredOnOutputScheduler() =>
104105
Exception? exception = null;
105106
fixture.ThrownExceptions.Subscribe(ex => exception = ex);
106107
fixture.Execute().Subscribe(_ => { }, _ => { });
107-
108108
Assert.Null(exception);
109109
scheduler.Start();
110110
Assert.IsType<InvalidOperationException>(exception);
@@ -194,11 +194,12 @@ public void ExecuteTicksThroughTheResults()
194194
/// </summary>
195195
[Fact]
196196
public void ResultIsTickedThroughSpecifiedScheduler() =>
197-
new TestScheduler().With(
197+
new TestScheduler().WithAsync(
198198
scheduler =>
199199
{
200+
// Allow scheduler to run freely
200201
var child1 = ReactiveCommand.Create(() => Observable.Return(1));
201-
var child2 = ReactiveCommand.Create(() => Observable.Return(2));
202+
var child2 = ReactiveCommand.CreateRunInBackground(() => Observable.Return(2));
202203
var childCommands = new[] { child1, child2 };
203204
var fixture = ReactiveCommand.CreateCombined(childCommands, outputScheduler: scheduler);
204205
fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe();
@@ -208,6 +209,7 @@ public void ResultIsTickedThroughSpecifiedScheduler() =>
208209

209210
scheduler.AdvanceByMs(1);
210211
Assert.Equal(1, results.Count);
212+
return Task.CompletedTask;
211213
});
212214
}
213215
}

src/ReactiveUI.Tests/Commands/ReactiveCommandTest.cs

Lines changed: 23 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -223,6 +223,26 @@ public void CreateThrowsIfExecutionParameterIsNull()
223223
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
224224
}
225225

226+
/// <summary>
227+
/// Creates the throws if execution parameter is null.
228+
/// </summary>
229+
[Fact]
230+
public void CreateRunInBackgroundThrowsIfExecutionParameterIsNull()
231+
{
232+
#pragma warning disable CS8625 // Cannot convert null literal to non-nullable reference type.
233+
#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
234+
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground(null));
235+
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<Unit>)null));
236+
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Action<Unit>)null));
237+
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<Unit, Unit>)null));
238+
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<IObservable<Unit>>)null));
239+
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<Task<Unit>>)null));
240+
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<Unit, IObservable<Unit>>)null));
241+
Assert.Throws<ArgumentNullException>(() => ReactiveCommand.CreateRunInBackground((Func<Unit, Task<Unit>>)null));
242+
#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
243+
#pragma warning restore CS8625 // Cannot convert null literal to non-nullable reference type.
244+
}
245+
226246
/// <summary>
227247
/// Exceptions the are delivered on output scheduler.
228248
/// </summary>
@@ -1059,17 +1079,18 @@ public void IsExecutingTicksAsExecutionsProgress() =>
10591079
/// </summary>
10601080
[Fact]
10611081
public void ResultIsTickedThroughSpecifiedScheduler() =>
1062-
new TestScheduler().With(
1082+
new TestScheduler().WithAsync(
10631083
scheduler =>
10641084
{
1065-
var fixture = ReactiveCommand.Create(() => Observables.Unit, outputScheduler: scheduler);
1085+
var fixture = ReactiveCommand.CreateRunInBackground(() => Observables.Unit, outputScheduler: scheduler);
10661086
fixture.ToObservableChangeSet(ImmediateScheduler.Instance).Bind(out var results).Subscribe();
10671087

10681088
fixture.Execute().Subscribe();
10691089
Assert.Empty(results);
10701090

10711091
scheduler.AdvanceByMs(1);
10721092
Assert.Equal(1, results.Count);
1093+
return Task.CompletedTask;
10731094
});
10741095

10751096
/// <summary>

src/ReactiveUI.Tests/Mocks/MockBindListViewModel.cs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
using System.Collections.ObjectModel;
88
using System.Linq;
99
using System.Reactive;
10+
using System.Reactive.Concurrency;
1011
using System.Reactive.Linq;
1112
using DynamicData;
1213

@@ -27,17 +28,16 @@ static MockBindListViewModel()
2728
/// </summary>
2829
public MockBindListViewModel()
2930
{
30-
SelectItem = ReactiveCommand.Create((MockBindListItemViewModel item) =>
31-
{
32-
ActiveListItem.Edit(l =>
33-
{
34-
var index = l.IndexOf(item);
35-
for (var i = l.Count - 1; i > index; i--)
31+
SelectItem = ReactiveCommand.Create(
32+
(MockBindListItemViewModel item) =>
33+
ActiveListItem.Edit(l =>
3634
{
37-
l.RemoveAt(i);
38-
}
39-
});
40-
});
35+
var index = l.IndexOf(item);
36+
for (var i = l.Count - 1; i > index; i--)
37+
{
38+
l.RemoveAt(i);
39+
}
40+
}));
4141

4242
ActiveListItem.Connect()
4343
.Select(_ => ActiveListItem.Count > 0 ? ActiveListItem.Items.ElementAt(ActiveListItem.Count - 1) : null)

src/ReactiveUI.Tests/Platforms/winforms/CommandBindingTests.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System;
77
using System.Reactive.Linq;
88
using System.Reactive.Subjects;
9+
using System.Threading.Tasks;
910
using System.Windows.Forms;
1011
using ReactiveUI.Winforms;
1112
using Xunit;
@@ -20,12 +21,13 @@ public class CommandBindingTests
2021
/// <summary>
2122
/// Tests that the command binder binds to button.
2223
/// </summary>
24+
/// <returns>A <see cref="Task"/> representing the asynchronous unit test.</returns>
2325
[Fact]
24-
public void CommandBinderBindsToButton()
26+
public async Task CommandBinderBindsToButtonAsync()
2527
{
2628
var fixture = new CreatesWinformsCommandBinding();
27-
var cmd = ReactiveCommand.Create<int>(_ => { });
28-
var input = new Button { };
29+
var cmd = ReactiveCommand.CreateRunInBackground<int>(_ => { });
30+
var input = new Button();
2931

3032
Assert.True(fixture.GetAffinityForObject(input.GetType(), true) > 0);
3133
Assert.True(fixture.GetAffinityForObject(input.GetType(), false) > 0);
@@ -40,7 +42,7 @@ public void CommandBinderBindsToButton()
4042
using (fixture.BindCommandToObject(cmd, input, Observable.Return((object)5)))
4143
{
4244
input.PerformClick();
43-
45+
await Task.Delay(10);
4446
Assert.True(commandExecuted);
4547
Assert.NotNull(ea);
4648
}
@@ -54,7 +56,7 @@ public void CommandBinderBindsToCustomControl()
5456
{
5557
var fixture = new CreatesWinformsCommandBinding();
5658
var cmd = ReactiveCommand.Create<int>(_ => { });
57-
var input = new CustomClickableControl { };
59+
var input = new CustomClickableControl();
5860

5961
Assert.True(fixture.GetAffinityForObject(input.GetType(), true) > 0);
6062
Assert.True(fixture.GetAffinityForObject(input.GetType(), false) > 0);
@@ -83,7 +85,7 @@ public void CommandBinderBindsToCustomComponent()
8385
{
8486
var fixture = new CreatesWinformsCommandBinding();
8587
var cmd = ReactiveCommand.Create<int>(_ => { });
86-
var input = new CustomClickableComponent { };
88+
var input = new CustomClickableComponent();
8789

8890
Assert.True(fixture.GetAffinityForObject(input.GetType(), true) > 0);
8991
Assert.True(fixture.GetAffinityForObject(input.GetType(), false) > 0);

src/ReactiveUI.Tests/Platforms/winforms/Mocks/WinformCommandBindViewModel.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ public class WinformCommandBindViewModel : ReactiveObject
1919
public WinformCommandBindViewModel()
2020
{
2121
_command1 = ReactiveCommand.Create(() => { }, outputScheduler: ImmediateScheduler.Instance);
22-
_command2 = ReactiveCommand.Create(() => { }, outputScheduler: ImmediateScheduler.Instance);
22+
_command2 = ReactiveCommand.CreateRunInBackground(() => { }, outputScheduler: ImmediateScheduler.Instance);
2323
_command3 = ReactiveCommand.Create<int>(i => ParameterResult = i * 10, outputScheduler: ImmediateScheduler.Instance);
2424
}
2525

0 commit comments

Comments
 (0)