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
7.99k stars 1.12k forks source link

[Bug]: Nested ItemsControl crashes entire app #3829

Closed christosk92 closed 1 month ago

christosk92 commented 1 month ago

Describe the bug 🐞

I am using ReactiveUI version 20.1.1 in WinUI

I have a layout something like this:

   <ScrollViewer>
       <ItemsRepeater ItemsSource="{x:Bind ViewModel.Groups}">
           <ItemsRepeater.ItemTemplate>
               <DataTemplate x:DataType="home:HomeItemGroup">
                       <ItemsRepeater ItemsSource="{x:Bind Items}"/>
               </DataTemplate>
           </ItemsRepeater.ItemTemplate>
       </ItemsRepeater>
   </ScrollViewer

With:

    _items
        .Connect()
        .DisposeMany()
        .Group(s => s.Group)
        .Transform(group => new HomeItemGroup(group.Key.Id,
            group.Key.Title,
            group.Key.Pinned,
            group.Cache.Connect()))
        //.AutoRefresh(x => x.Pinned)
        .Sort(SortExpressionComparer<HomeItemGroup>.Ascending(x => x.Title))
        .Bind(out _groups)
        .DisposeMany()
        .ObserveOn(RxApp.MainThreadScheduler)
        .Subscribe();

If I reload the sourceCache (_items) with new data, the app crashes in its entirety without a meaningful message.

I cloned the repo to try and find out what goes wrong, and it seems that the DispatcherQueueScheduler.cs class is to blame, specifically the function on line 78: d.Disposable = action(this, state);

This throws an exception: COMException, which upon inspecting the stacktrace, contains the following:

   at WinRT.ExceptionHelpers.<ThrowExceptionForHR>g__Throw|38_0(Int32 hr)
   at WinRT.ExceptionHelpers.ThrowExceptionForHR(Int32 hr)
   at ABI.Microsoft.UI.Xaml.Interop.WinRTNotifyCollectionChangedEventArgsRuntimeClassFactory.CreateInstanceWithAllParameters(NotifyCollectionChangedAction action, IList newItems, IList oldItems, Int32 newIndex, Int32 oldIndex)
   at ABI.System.Collections.Specialized.NotifyCollectionChangedEventArgs.CreateMarshaler2(NotifyCollectionChangedEventArgs value)
   at ABI.System.Collections.Specialized.NotifyCollectionChangedEventHandler.NativeDelegateWrapper.Invoke(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ReadOnlyObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs args)
   at System.Collections.ObjectModel.ReadOnlyObservableCollection`1.HandleCollectionChanged(Object sender, NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at DynamicData.Binding.ObservableCollectionExtended`1.OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   at System.Collections.ObjectModel.ObservableCollection`1.RemoveItem(Int32 index)
   at System.Collections.ObjectModel.Collection`1.RemoveAt(Int32 index)
   at DynamicData.Binding.SortedObservableCollectionAdaptor`2.DoUpdate(ISortedChangeSet`2 updates, IObservableCollection`1 list)
   at DynamicData.Binding.SortedObservableCollectionAdaptor`2.Adapt(ISortedChangeSet`2 changes, IObservableCollection`1 collection)
   at DynamicData.ObservableCacheEx.<>c__DisplayClass33_0`2.<Bind>b__1(ISortedChangeSet`2 changes)
   at System.Reactive.Linq.ObservableImpl.Select`2.Selector._.OnNext(TSource value)
--- End of stack trace from previous location ---
   at System.Reactive.PlatformServices.ExceptionServicesImpl.Rethrow(Exception exception)
   at System.Reactive.ExceptionHelpers.Throw(Exception exception)
   at System.Reactive.Stubs.<>c.<.cctor>b__2_1(Exception ex)
   at System.Reactive.AnonymousSafeObserver`1.OnError(Exception error)
   at System.Reactive.Sink`1.ForwardOnError(Exception error)
   at System.Reactive.ObserveOnObserverNew`1.DrainStep(ConcurrentQueue`1 q)
   at System.Reactive.ObserveOnObserverNew`1.DrainShortRunning(IScheduler recursiveScheduler)
   at System.Reactive.ObserveOnObserverNew`1.<>c.<.cctor>b__17_0(IScheduler scheduler, ObserveOnObserverNew`1 self)
   at System.Reactive.Concurrency.DispatcherQueueScheduler.<>c__DisplayClass10_0`1.<Schedule>b__0() in C:\Users\chris\Documents\Personal\ReactiveUI\src\ReactiveUI.Maui\WinUI\DispatcherQueueScheduler.cs:line 94

Is this an issue with:

  1. WinUI
  2. ReactiveUI
  3. DynamicData

All help/thoughts are appreciated

Step to reproduce

  1. Go to '...'
  2. Click on '....'
  3. Scroll down to '....'
  4. See error

Reproduction repository

No response

Expected behavior

No crash !

Screenshots 🖼️

No response

IDE

No response

Operating system

No response

Version

No response

Device

No response

ReactiveUI Version

No response

Additional information ℹ️

No response

christosk92 commented 1 month ago

It throws a COMException with HRESULT: -2147417842(0x8001010E) which is: RPC_E_WRONG_THREAD

christosk92 commented 1 month ago

I was able to clone DynamicData and point it down to: image

christosk92 commented 1 month ago

Ok so basically what went wrong is, I was editing the list from a non UI thread which I guess caused this issue..
Wrapping the Edit function in a UI synchronization block fixed it for now. I would love to see a better error message tho !

github-actions[bot] commented 1 month ago

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.