reactiveui / ReactiveUI

An advanced, composable, functional reactive model-view-viewmodel framework for all .NET platforms that is inspired by functional reactive programming. ReactiveUI allows you to abstract mutable state away from your user interfaces, express the idea around a feature in one readable place and improve the testability of your application.
https://www.reactiveui.net
MIT License
8.08k stars 1.12k forks source link

[Bug]: iOS threading exception on command execution. #3754

Open thombrink opened 8 months ago

thombrink commented 8 months ago

Describe the bug 🐞

Executing a command inside an iOS app throws an UIKit.UIKitThreadAccessException: 'UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread.' exception.

Step to reproduce

  1. Create a new MAUI project and install the ReactiveUI.Maui nuget
  2. Create a viewmodel with a reactive command
  3. Create a page and bind the viewmodel
  4. Create a toolbar item on that page and bind the command (via code behind)
  5. Start the app and touch the button
  6. Get anUIKit.UIKitThreadAccessException: 'UIKit Consistency error: you are calling a UIKit method that can only be invoked from the UI thread.' exception.

Stack trace: at UIKit.UIApplication.EnsureUIThread() in /Users/builder/azdo/_work/1/s/xamarin-macios/src/UIKit/UIApplication.cs:line 111 at UIKit.UIBarButtonItem.set_Enabled(Boolean value) in /Users/builder/azdo/_work/1/s/xamarin-macios/src/build/dotnet/ios/generated-sources/UIKit/UIBarButtonItem.g.cs:line 1036 at Microsoft.Maui.Controls.Compatibility.Platform.iOS.ToolbarItemExtensions.PrimaryToolbarItem.UpdateIsEnabled() at Microsoft.Maui.Controls.Compatibility.Platform.iOS.ToolbarItemExtensions.PrimaryToolbarItem.OnPropertyChanged(Object sender, PropertyChangedEventArgs e) at Microsoft.Maui.Controls.BindableObject.OnPropertyChanged(String propertyName) at Microsoft.Maui.Controls.Element.OnPropertyChanged(String propertyName) at Microsoft.Maui.Controls.BindableObject.SetValueActual(BindableProperty property, BindablePropertyContext context, Object value, Boolean currentlyApplying, SetValueFlags attributes, SetterSpecificity specificity, Boolean silent) at Microsoft.Maui.Controls.BindableObject.SetValueCore(BindableProperty property, Object value, SetValueFlags attributes, SetValuePrivateFlags privateAttributes, SetterSpecificity specificity) at Microsoft.Maui.Controls.BindableObject.SetValue(BindableProperty property, Object value, SetterSpecificity specificity) at Microsoft.Maui.Controls.BindableObjectExtensions.RefreshPropertyValue(BindableObject self, BindableProperty property, Object value) at Microsoft.Maui.Controls.MenuItem.Microsoft.Maui.Controls.Internals.ICommandElement.CanExecuteChanged(Object sender, EventArgs e) at ReactiveUI.ReactiveCommandBase`2[[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263],[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263]].OnCanExecuteChanged(Boolean newValue) in /_/src/ReactiveUI/ReactiveCommand/ReactiveCommandBase.cs:line 143 at System.Reactive.AnonymousSafeObserver`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Sink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ForwardOnNext(Boolean value) at System.Reactive.IdentitySink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Subjects.FastImmediateObserver`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].EnsureActive(Int32 count) at System.Reactive.Subjects.FastImmediateObserver`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].EnsureActive() at System.Reactive.Subjects.ReplaySubject`1.ReplayBase[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Subjects.ReplaySubject`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Sink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ForwardOnNext(Boolean value) at System.Reactive.IdentitySink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Sink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ForwardOnNext(Boolean value) at System.Reactive.Linq.ObservableImpl.DistinctUntilChanged`2._[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Sink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ForwardOnNext(Boolean value) at System.Reactive.Linq.ObservableImpl.CombineLatest`3._.SecondObserver[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Sink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ForwardOnNext(Boolean value) at System.Reactive.IdentitySink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Subjects.FastImmediateObserver`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].EnsureActive(Int32 count) at System.Reactive.Subjects.FastImmediateObserver`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].EnsureActive() at System.Reactive.Subjects.ReplaySubject`1.ReplayBase[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Subjects.ReplaySubject`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Sink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ForwardOnNext(Boolean value) at System.Reactive.IdentitySink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Sink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ForwardOnNext(Boolean value) at System.Reactive.Linq.ObservableImpl.DistinctUntilChanged`2._[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Sink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ForwardOnNext(Boolean value) at System.Reactive.IdentitySink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Boolean value) at System.Reactive.Sink`1[[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ForwardOnNext(Boolean value) at System.Reactive.Linq.ObservableImpl.Select`2.Selector._[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e],[System.Boolean, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(Int32 value) at System.Reactive.Sink`1[[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].ForwardOnNext(Int32 value) at System.Reactive.Linq.ObservableImpl.Scan`2._[[ReactiveUI.ReactiveCommand`2.ExecutionInfo[[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263],[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263]], ReactiveUI, Version=19.5.0.0, Culture=neutral, PublicKeyToken=null],[System.Int32, System.Private.CoreLib, Version=8.0.0.0, Culture=neutral, PublicKeyToken=7cec85d7bea7798e]].OnNext(ExecutionInfo value) at System.Reactive.SafeObserver`1.WrappingSafeObserver[[ReactiveUI.ReactiveCommand`2.ExecutionInfo[[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263],[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263]], ReactiveUI, Version=19.5.0.0, Culture=neutral, PublicKeyToken=null]].OnNext(ExecutionInfo value) at System.Reactive.Sink`1[[ReactiveUI.ReactiveCommand`2.ExecutionInfo[[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263],[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263]], ReactiveUI, Version=19.5.0.0, Culture=neutral, PublicKeyToken=null]].ForwardOnNext(ExecutionInfo value) at System.Reactive.ObserveOnObserverLongRunning`1[[ReactiveUI.ReactiveCommand`2.ExecutionInfo[[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263],[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263]], ReactiveUI, Version=19.5.0.0, Culture=neutral, PublicKeyToken=null]].Drain() at System.Reactive.ObserveOnObserverLongRunning`1.<>c[[ReactiveUI.ReactiveCommand`2.ExecutionInfo[[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263],[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263]], ReactiveUI, Version=19.5.0.0, Culture=neutral, PublicKeyToken=null]].<.cctor>b__17_0(ObserveOnObserverLongRunning`1 self, ICancelable cancelable) at System.Reactive.Concurrency.DefaultScheduler.LongRunning.LongScheduledWorkItem`1.<>c[[System.Reactive.ObserveOnObserverLongRunning`1[[ReactiveUI.ReactiveCommand`2.ExecutionInfo[[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263],[System.Reactive.Unit, System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263]], ReactiveUI, Version=19.5.0.0, Culture=neutral, PublicKeyToken=null]], System.Reactive, Version=6.0.0.0, Culture=neutral, PublicKeyToken=94bc3704cddfc263]].<.ctor>b__3_0(Object thisObject) at System.Reactive.Concurrency.ConcurrencyAbstractionLayerImpl.<>c.<StartThread>b__8_0(Object itemObject) at System.Threading.Thread.StartCallback()

Reproduction repository

Can be created if desired

Expected behavior

To not thow an exception on executing a ReactiveCommand.

Screenshots 🖼️

No response

IDE

Visual Studio 2022

Operating system

iOS

Version

.net8.0

Device

iphone 12 mini

ReactiveUI Version

ReactiveUI.Maui 19.5.41

Additional information ℹ️

No response

anaisbetts commented 8 months ago

Verify your schedulers are set correctly (i.e. you have the right ReactiveUI libraries referenced) because it looks like they are not

thombrink commented 8 months ago

The RxApp main thread scheduler context is equal to my main thread context, as fas as I know. I only included the reactive ui Maui nuget found here: https://www.nuget.org/packages/ReactiveUI.Maui what am I missing?

ReactiveUI.Maui 19.5.41
Contains the ReactiveUI platform specific extensions for Microsoft Maui
thombrink commented 8 months ago

I accidentally closed it

dpvreony commented 8 months ago

possible dupe of #3473 from what i can see of the change history last time this is a MAUI issue possibly relating to https://github.com/dotnet/maui/issues/16862 which links to various reoccurances such as https://github.com/dotnet/maui/issues/17034

do you have a minimal repro?