I' exploring ReactiveUI (with WPF) and made a minimal example which I don't get to work. The Command Command
creates a string which should be assigned to the property Result
. The property is bound in the UI to a TextBlock
, the Command to a Button
.
public class MainWindowViewModel : ReactiveObject
{
public MainWindowViewModel()
{
this.Command = ReactiveCommand.CreateFromTask(this.CommandExecutedAsync);
this.result = this.Command
.ObserveOn(RxApp.MainThreadScheduler)
.ToProperty(this, x => x.Result);
}
public ReactiveCommand<Unit, string> Command { get; }
private ObservableAsPropertyHelper<string> result;
public string Result => this.result.Value;
private async Task<string> CommandExecutedAsync()
{
await Task.Delay(1000);
return DateTimeOffset.UtcNow.ToString();
}
}
So far so good. If I'm pressing the button the command is executed but when the property Result
is set an exception is raised b/c I'm not in the main thread.
I understand that this is a problem but I've thought the ObserveOn(RxApp.MainThreadScheduler)
would have put me there. I've also tried to inject the scheduler in ToProperty
but this didn't change the outcome.
Edit: Adding execption details:
System.InvalidOperationException
HResult=0x80131509
Message=The calling thread cannot access this object because a different thread owns it.
Source=WindowsBase
StackTrace:
at System.Windows.Threading.Dispatcher.VerifyAccess()
at System.Windows.DependencyObject.GetValue(DependencyProperty dp)
at System.Windows.Controls.Primitives.ButtonBase.get_Command()
at System.Windows.Controls.Primitives.ButtonBase.UpdateCanExecute()
at System.Windows.Controls.Primitives.ButtonBase.OnCanExecuteChanged(Object sender, EventArgs e)
at System.Windows.Input.CanExecuteChangedEventManager.HandlerSink.OnCanExecuteChanged(Object sender, EventArgs e)
at System.Reactive.AnonymousSafeObserver`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/AnonymousSafeObserver.cs:line 44
at System.Reactive.Subjects.FastImmediateObserver`1.EnsureActive(Int32 count) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 863
at System.Reactive.Subjects.ReplaySubject`1.ReplayBase.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 272
at System.Reactive.Linq.ObservableImpl.CombineLatest`3._.SecondObserver.OnNext(TSecond value) in /_/Rx.NET/Source/src/System.Reactive/Linq/Observable/CombineLatest.cs:line 177
at System.Reactive.Subjects.FastImmediateObserver`1.EnsureActive(Int32 count) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 863
at System.Reactive.Subjects.ReplaySubject`1.ReplayBase.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 272
at System.Reactive.SafeObserver`1.WrappingSafeObserver.OnNext(TSource value) in /_/Rx.NET/Source/src/System.Reactive/Internal/SafeObserver.cs:line 31
at System.Reactive.ObserveOnObserverLongRunning`1.Drain() in /_/Rx.NET/Source/src/System.Reactive/Internal/ScheduledObserver.cs:line 735
at System.Threading.Thread.StartHelper.Callback(Object state)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
--- End of stack trace from previous location ---
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.Thread.StartCallback()
Also I've pushed a minimal SLN to github with the code: https://github.com/wgross/wpf-reactive.git
Your case has been tricky for me. I made some tests, and it looks like you didn't add the ReactiveUi.WPF
NuGet package.
Side notes (if you are learning ReactiveUi, you will need this):
ReactiveUi
has its binding system, which is very useful.ReactiveWindow
, ReactiveControl
).