Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Splat.Microsoft.Extensions.DependencyInjection causes System.InvalidOperationException: Call from invalid thread #1024

Closed
Leon99 opened this issue Jul 18, 2023 · 16 comments

Comments

@Leon99
Copy link

Leon99 commented Jul 18, 2023

Just trying to put together Avalonia's routing sample and Splat.Microsoft.Extensions.DependencyInjection. Pressing Go next button throws the exception below when Splat.Microsoft.Extensions.DependencyInjection is used. Works fine if services.UseMicrosoftDependencyResolver(); is commented out.

Repro: AvaRxUI.zip

System.InvalidOperationException: Call from invalid thread
   at Avalonia.Threading.Dispatcher.<VerifyAccess>g__ThrowVerifyAccess|16_0() in /_/src/Avalonia.Base/Threading/Dispatcher.cs:line 77
   at Avalonia.Threading.Dispatcher.VerifyAccess() in /_/src/Avalonia.Base/Threading/Dispatcher.cs:line 78
   at Avalonia.AvaloniaObject.VerifyAccess() in /_/src/Avalonia.Base/AvaloniaObject.cs:line 111
   at Avalonia.AvaloniaObject.GetValue[T](StyledProperty`1 property) in /_/src/Avalonia.Base/AvaloniaObject.cs:line 226
   at Avalonia.Controls.Button.get_Command() in /_/src/Avalonia.Controls/Button.cs:line 138
   at Avalonia.Controls.Button.CanExecuteChanged(Object sender, EventArgs e) in /_/src/Avalonia.Controls/Button.cs:line 560
   at ReactiveUI.ReactiveCommandBase`2.OnCanExecuteChanged(Boolean newValue) in /_/src/ReactiveUI/ReactiveCommand/ReactiveCommandBase.cs:line 219
   at System.Reactive.AnonymousSafeObserver`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/AnonymousSafeObserver.cs:line 43
   at System.Reactive.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   at System.Reactive.IdentitySink`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Internal/IdentitySink.cs:line 15
   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.FastImmediateObserver`1.EnsureActive() in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 765
   at System.Reactive.Subjects.ReplaySubject`1.ReplayBase.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 270
   at System.Reactive.Subjects.ReplaySubject`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 161
   at System.Reactive.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   at System.Reactive.IdentitySink`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Internal/IdentitySink.cs:line 15
   at System.Reactive.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   at System.Reactive.Linq.ObservableImpl.DistinctUntilChanged`2._.OnNext(TSource value) in /_/Rx.NET/Source/src/System.Reactive/Linq/Observable/DistinctUntilChanged.cs:line 72
   at System.Reactive.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   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.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   at System.Reactive.IdentitySink`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Internal/IdentitySink.cs:line 15
   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.FastImmediateObserver`1.EnsureActive() in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 765
   at System.Reactive.Subjects.ReplaySubject`1.ReplayBase.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 270
   at System.Reactive.Subjects.ReplaySubject`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Subjects/ReplaySubject.cs:line 161
   at System.Reactive.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   at System.Reactive.IdentitySink`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Internal/IdentitySink.cs:line 15
   at System.Reactive.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   at System.Reactive.Linq.ObservableImpl.DistinctUntilChanged`2._.OnNext(TSource value) in /_/Rx.NET/Source/src/System.Reactive/Linq/Observable/DistinctUntilChanged.cs:line 72
   at System.Reactive.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   at System.Reactive.IdentitySink`1.OnNext(T value) in /_/Rx.NET/Source/src/System.Reactive/Internal/IdentitySink.cs:line 15
   at System.Reactive.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   at System.Reactive.Linq.ObservableImpl.Select`2.Selector._.OnNext(TSource value) in /_/Rx.NET/Source/src/System.Reactive/Linq/Observable/Select.cs:line 47
   at System.Reactive.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   at System.Reactive.Linq.ObservableImpl.Scan`2._.OnNext(TSource value) in /_/Rx.NET/Source/src/System.Reactive/Linq/Observable/Scan.cs:line 48
   at System.Reactive.SafeObserver`1.WrappingSafeObserver.OnNext(TSource value) in /_/Rx.NET/Source/src/System.Reactive/Internal/SafeObserver.cs:line 30
   at System.Reactive.Sink`1.ForwardOnNext(TTarget value) in /_/Rx.NET/Source/src/System.Reactive/Internal/Sink.cs:line 49
   at System.Reactive.ObserveOnObserverLongRunning`1.Drain() in /_/Rx.NET/Source/src/System.Reactive/Internal/ScheduledObserver.cs:line 732
   at System.Reactive.ObserveOnObserverLongRunning`1.<>c.<.cctor>b__17_0(ObserveOnObserverLongRunning`1 self, ICancelable cancelable) in /_/Rx.NET/Source/src/System.Reactive/Internal/ScheduledObserver.cs:line 680
   at System.Reactive.Concurrency.DefaultScheduler.LongRunning.LongScheduledWorkItem`1.<>c.<.ctor>b__3_0(Object thisObject) in /_/Rx.NET/Source/src/System.Reactive/Concurrency/DefaultScheduler.cs:line 181
   at System.Reactive.Concurrency.ConcurrencyAbstractionLayerImpl.<>c.<StartThread>b__8_0(Object itemObject) in /_/Rx.NET/Source/src/System.Reactive/Concurrency/ConcurrencyAbstractionLayerImpl.cs:line 75
@glennawatson
Copy link
Contributor

I'm guessing what's happening is Avalonia is sensitive that the logic in Avalonia.Controls.Button.CanExecuteChanged be run on the main UI thread.

Part of the CanExecute observable called into ReactiveCommand is being evaluated on another thread which is causing issues with Avalonia.

The Avalonia team maintain their own RxUI plugin but I'd imagine they might have a extension to convert an observable to run on the main ui thread.

@Leon99
Copy link
Author

Leon99 commented Jul 18, 2023

Thanks for the quick response @glennawatson!

Why do you reckon it doesn't happen when using Splat's built-in resolver?

@Leon99
Copy link
Author

Leon99 commented Jul 18, 2023

Same issue with Splat.DryIoc.

@glennawatson
Copy link
Contributor

Splat tends to be a dictionary based system that creates on whatever thread is calling it. So it'll call a Func method, so could be to do with that.

I can have a look later today.

@Leon99
Copy link
Author

Leon99 commented Jul 19, 2023

I used a Func for MS DI (you can see it in the repro code) but not with DryIoc. This is the code:

        var container = new Container();
        container.Register<MainWindowViewModel>();
        container.Register<FirstViewModel>();
        container.Register<IViewFor<FirstViewModel>, FirstView>();
        container.RegisterMapping<IScreen, MainWindowViewModel>();
        container.UseDryIocDependencyResolver();

Happy to do some debugging if you point me in the right direction.

@glennawatson
Copy link
Contributor

Let me have a look properly tomorrow for you. We are in the same Timezone. @ChrisPulman may have time in our evenings if life permits.

@ChrisPulman
Copy link
Member

I can try to take a look this evening, having the repo is really helpful, thank you.

@ChrisPulman
Copy link
Member

AvaRxUI_V2.zip
Please try this version, hopefully this will make things easier for you.

@ChrisPulman
Copy link
Member

AvaloniaUI/Avalonia#12261
I have created a PR, hopefully the Avalonia Team will accept it.
Meanwhile <PackageReference Include="CP.Avalonia.ReactiveUI.Splat.DryIoc" Version="1.0.0" /> OR <PackageReference Include="CP.Avalonia.ReactiveUI.Splat.MicrosoftDependencyResolver" Version="1.0.0" /> could be used.

@ChrisPulman
Copy link
Member

Avalonia have decided that they don't want to add the suggested packages to their codebase.
We will see if there's a better home for this otherwise I will continue to provide the necessary support from my repository.

@glennawatson
Copy link
Contributor

I guess we could take the hit somewhere in the rxui repo so it's more official.

@glennawatson
Copy link
Contributor

Discussed with @ChrisPulman offline, we'll make official Splat packages supporting the Avalonia use case for the different DI containers. It'll be minimal invasive to our stuff.

@Leon99
Copy link
Author

Leon99 commented Jul 20, 2023

Thanks @ChrisPulman & @glennawatson, much appreciated!

Just a couple of things after looking at the code:

  • Is RxApp.MainThreadScheduler = AvaloniaScheduler.Instance; going to affect something else other then the DI container? If so, it's a bit questionable to set it inside UseSplatWith... as it might cause unexpected side-effects. On the other hand, I'm not an expert but looking at the XML documentation for both RxApp.MainThreadScheduler and AvaloniaScheduler.Instance gives me an impression that they are supposed to be equal regardless. If so, isn't that a responsibility of Avalonia.ReactiveUI to set?
  • Given that it's just a few lines of code, could it be better to package the .cs as content, and avoid overhead of having another dll?

@ChrisPulman
Copy link
Member

The end Splat packages will be a single alternative package for Avalonia and not an additional one, and be an extension of the existing code with a single dll.

RxApp.MainThreadScheduler should be set to AvaloniaScheduler.Instance and is done in the UseReactiveUI section of Avalonia.ReactiveUI, however when initialisation of Splat with the new DI occurs the RxApp.MainThreadScheduler is reset causing the issue you experienced.
We will need to document the order in which the AppBuilder extensions are added in order to ensure that things are initialised in the correct order. I will most likely wrap the UseReactiveUI inside the extension to be certain it's done in the right order. Something like UseReactiveUiWithDryIoc(container => {})

@ChrisPulman ChrisPulman mentioned this issue Jul 21, 2023
2 tasks
@ChrisPulman
Copy link
Member

The following packages have been released:
ReactiveUI.Avalonia
ReactiveUI.Avalonia.DryIoc
ReactiveUI.Avalonia.Autofac
ReactiveUI.Avalonia.Ninject
ReactiveUI.Avalonia.Microsoft.Extensions.DependencyInjection

Each is a single dll that replaces the need to include either Avalonia.ReactiveUI or the relevant Splat container package.
Only one call to UseReactiveUI OR UseReactiveUIWithxxxxxx OR UseReactiveUIWithDIContainer (where xxxxxx is the DI container) should be used with the AppBuilder.

@github-actions
Copy link

github-actions bot commented Aug 8, 2023

This issue has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Aug 8, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants