Skip to content

Commit 78cca23

Browse files
authored
feat: support ReactiveCommand background execution (#473)
feat: support background ReactiveCommands Implementation: - add the RunInBackground ReactiveCommand attribute option - emit CreateRunInBackground for synchronous command methods - preserve task and observable command factories - handle output scheduler overloads across ReactiveUI 23 and 24 Tests: - add TUnit generator and integration coverage - verify ReactiveUI 23, v24 Primitives, and v24 System.Reactive compatibility - update generated command attribute snapshots Documentation: - document background command generation and supported method behavior
1 parent 3421e34 commit 78cca23

39 files changed

Lines changed: 547 additions & 4 deletions

File tree

‎README.md‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ ReactiveUI Source Generators automatically generate ReactiveUI objects to stream
4545
- `[ObservableAsProperty(PropertyName = "ReadOnlyPropertyName")]`
4646
- `[ObservableAsProperty(InitialValue = "Default Value")]` Only valid for partial properties using (C# 13 Visual Studio Version 17.12.0)
4747
- `[ReactiveCommand]`
48+
- `[ReactiveCommand(RunInBackground = true)]` runs a synchronous command on ReactiveUI's background scheduler
4849
- `[ReactiveCommand(CanExecute = nameof(IObservableBoolName))]` with CanExecute
4950
- `[ReactiveCommand(OutputScheduler = "RxSchedulers.MainThreadScheduler")]` using a ReactiveUI Scheduler
5051
- `[ReactiveCommand(OutputScheduler = nameof(_isheduler))]` using a Scheduler defined in the class
@@ -458,6 +459,20 @@ public partial class MyReactiveClass
458459
}
459460
```
460461

462+
### Usage ReactiveCommand on the background scheduler
463+
464+
Use `RunInBackground` for synchronous command methods that should be created with `ReactiveCommand.CreateRunInBackground`. Task- and observable-returning methods continue to use their asynchronous ReactiveCommand factories.
465+
466+
```csharp
467+
using ReactiveUI.SourceGenerators;
468+
469+
public partial class MyReactiveClass
470+
{
471+
[ReactiveCommand(RunInBackground = true)]
472+
private void ExecuteExpensiveWork() { }
473+
}
474+
```
475+
461476
### Usage ReactiveCommand with parameter and return value
462477
```csharp
463478
using ReactiveUI.SourceGenerators;

‎src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.Access#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ internal sealed class ReactiveCommandAttribute : global::System.Attribute
3232
///
3333
public string? OutputScheduler { get; init; }
3434

35+
///
36+
/// Gets a value indicating whether a synchronous method runs on ReactiveUI's background scheduler.
37+
///
38+
///
39+
/// to create the command with ReactiveCommand.CreateRunInBackground.
40+
///
41+
public bool RunInBackground { get; init; }
42+
3543
///
3644
/// Gets the AccessModifier of the ReactiveCommand property.
3745
///

‎src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.AsyncWithParam#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ internal sealed class ReactiveCommandAttribute : global::System.Attribute
3232
///
3333
public string? OutputScheduler { get; init; }
3434

35+
///
36+
/// Gets a value indicating whether a synchronous method runs on ReactiveUI's background scheduler.
37+
///
38+
///
39+
/// to create the command with ReactiveCommand.CreateRunInBackground.
40+
///
41+
public bool RunInBackground { get; init; }
42+
3543
///
3644
/// Gets the AccessModifier of the ReactiveCommand property.
3745
///

‎src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.Basic#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ internal sealed class ReactiveCommandAttribute : global::System.Attribute
3232
///
3333
public string? OutputScheduler { get; init; }
3434

35+
///
36+
/// Gets a value indicating whether a synchronous method runs on ReactiveUI's background scheduler.
37+
///
38+
///
39+
/// to create the command with ReactiveCommand.CreateRunInBackground.
40+
///
41+
public bool RunInBackground { get; init; }
42+
3543
///
3644
/// Gets the AccessModifier of the ReactiveCommand property.
3745
///

‎src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.FromReactiveAsyncCommand#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ internal sealed class ReactiveCommandAttribute : global::System.Attribute
3232
///
3333
public string? OutputScheduler { get; init; }
3434

35+
///
36+
/// Gets a value indicating whether a synchronous method runs on ReactiveUI's background scheduler.
37+
///
38+
///
39+
/// to create the command with ReactiveCommand.CreateRunInBackground.
40+
///
41+
public bool RunInBackground { get; init; }
42+
3543
///
3644
/// Gets the AccessModifier of the ReactiveCommand property.
3745
///

‎src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.Nested#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ internal sealed class ReactiveCommandAttribute : global::System.Attribute
3232
///
3333
public string? OutputScheduler { get; init; }
3434

35+
///
36+
/// Gets a value indicating whether a synchronous method runs on ReactiveUI's background scheduler.
37+
///
38+
///
39+
/// to create the command with ReactiveCommand.CreateRunInBackground.
40+
///
41+
public bool RunInBackground { get; init; }
42+
3543
///
3644
/// Gets the AccessModifier of the ReactiveCommand property.
3745
///

‎src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.NullableTypeReturn#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ internal sealed class ReactiveCommandAttribute : global::System.Attribute
3232
///
3333
public string? OutputScheduler { get; init; }
3434

35+
///
36+
/// Gets a value indicating whether a synchronous method runs on ReactiveUI's background scheduler.
37+
///
38+
///
39+
/// to create the command with ReactiveCommand.CreateRunInBackground.
40+
///
41+
public bool RunInBackground { get; init; }
42+
3543
///
3644
/// Gets the AccessModifier of the ReactiveCommand property.
3745
///
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
//HintName: ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.cs
2+
// Copyright (c) 2026 .NET Foundation and Contributors. All rights reserved.
3+
// Licensed to the .NET Foundation under one or more agreements.
4+
// The .NET Foundation licenses this file to you under the MIT license.
5+
// See the LICENSE file in the project root for full license information.
6+
7+
//
8+
#pragma warning disable
9+
#nullable enable
10+
namespace ReactiveUI.SourceGenerators;
11+
12+
///
13+
/// ReactiveCommand Attribute.
14+
///
15+
///
16+
[global::System.AttributeUsage(global::System.AttributeTargets.Method, AllowMultiple = false, Inherited = false)]
17+
internal sealed class ReactiveCommandAttribute : global::System.Attribute
18+
{
19+
///
20+
/// Gets the can execute method or property.
21+
///
22+
///
23+
/// The name of the CanExecute Observable of bool.
24+
///
25+
public string? CanExecute { get; init; }
26+
27+
///
28+
/// Gets the output scheduler.
29+
///
30+
///
31+
/// The output scheduler.
32+
///
33+
public string? OutputScheduler { get; init; }
34+
35+
///
36+
/// Gets a value indicating whether a synchronous method runs on ReactiveUI's background scheduler.
37+
///
38+
///
39+
/// to create the command with ReactiveCommand.CreateRunInBackground.
40+
///
41+
public bool RunInBackground { get; init; }
42+
43+
///
44+
/// Gets the AccessModifier of the ReactiveCommand property.
45+
///
46+
///
47+
/// The AccessModifier of the property.
48+
///
49+
public PropertyAccessModifier AccessModifier { get; init; }
50+
}
51+
#nullable restore
52+
#pragma warning restore
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//HintName: TestNs.TestVM.ReactiveCommands.g.cs
2+
//
3+
4+
#pragma warning disable
5+
#nullable enable
6+
7+
namespace TestNs
8+
{
9+
10+
public partial class TestVM
11+
{
12+
private global::ReactiveUI.ReactiveCommand<global::ReactiveUI.Primitives.RxVoid, global::ReactiveUI.Primitives.RxVoid>? _saveCommand;
13+
14+
15+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
16+
public global::ReactiveUI.ReactiveCommand<global::ReactiveUI.Primitives.RxVoid, global::ReactiveUI.Primitives.RxVoid> SaveCommand { get => _saveCommand ??= global::ReactiveUI.ReactiveCommand.CreateRunInBackground(Save); }
17+
private global::ReactiveUI.ReactiveCommand<int, int>? _calculateCommand;
18+
19+
20+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
21+
public global::ReactiveUI.ReactiveCommand<int, int> CalculateCommand { get => _calculateCommand ??= global::ReactiveUI.ReactiveCommand.CreateRunInBackground<int, int>(Calculate); }
22+
private global::ReactiveUI.ReactiveCommand<int, string>? _formatCommand;
23+
24+
25+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
26+
public global::ReactiveUI.ReactiveCommand<int, string> FormatCommand { get => _formatCommand ??= global::ReactiveUI.ReactiveCommand.CreateRunInBackground<int, string>(Format, CanRun, backgroundScheduler: null, outputScheduler: global::ReactiveUI.RxSchedulers.MainThreadScheduler); }
27+
private global::ReactiveUI.ReactiveCommand<global::ReactiveUI.Primitives.RxVoid, global::ReactiveUI.Primitives.RxVoid>? _foregroundCommand;
28+
29+
30+
[global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage]
31+
public global::ReactiveUI.ReactiveCommand<global::ReactiveUI.Primitives.RxVoid, global::ReactiveUI.Primitives.RxVoid> ForegroundCommand { get => _foregroundCommand ??= global::ReactiveUI.ReactiveCommand.Create(Foreground); }
32+
}
33+
}
34+
#nullable restore
35+
#pragma warning restore

‎src/ReactiveUI.SourceGenerator.Tests/REACTIVECMD/ReactiveCMDGeneratorTests.Scheduler#ReactiveUI.SourceGenerators.ReactiveCommandAttribute.g.verified.cs‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,14 @@ internal sealed class ReactiveCommandAttribute : global::System.Attribute
3232
///
3333
public string? OutputScheduler { get; init; }
3434

35+
///
36+
/// Gets a value indicating whether a synchronous method runs on ReactiveUI's background scheduler.
37+
///
38+
///
39+
/// to create the command with ReactiveCommand.CreateRunInBackground.
40+
///
41+
public bool RunInBackground { get; init; }
42+
3543
///
3644
/// Gets the AccessModifier of the ReactiveCommand property.
3745
///

0 commit comments

Comments
 (0)