beto-rodriguez / LiveCharts2

Simple, flexible, interactive & powerful charts, maps and gauges for .Net, LiveCharts2 can now practically run everywhere Maui, Uno Platform, Blazor-wasm, WPF, WinForms, Xamarin, Avalonia, WinUI, UWP.
https://livecharts.dev
MIT License
4.39k stars 573 forks source link

LiveChartsCore.SkiaSharpView.Avalonia crashes on frequent value update #1049

Closed Developer-Alexander closed 1 year ago

Developer-Alexander commented 1 year ago

Describe the bug When the Valus of a series are updated very frequently via an observable collection the Avalonia App crashes with Collection was modified; enumeration operation may not execute..

The version of Avalonia that is used is 11.0.0-rc1.1. The version of LiveChartsCore.SkiaSharpView.Avalonia that is used is 2.0.0-beta.700-11.0.0-rc1.1.

System.InvalidOperationException
  HResult=0x80131509
  Nachricht = Collection was modified; enumeration operation may not execute.
  Quelle = System.Private.CoreLib
  Stapelüberwachung:
   bei System.ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
   bei System.Collections.Generic.List`1.Enumerator.MoveNextRare()
   bei LiveChartsCore.Kernel.Providers.DataFactory`2.<EnumerateChartEntities>d__18.MoveNext()
   bei LiveChartsCore.Kernel.Providers.DataFactory`2.<Fetch>d__10.MoveNext()
   bei System.Linq.Enumerable.WhereSelectEnumerableIterator`2.ToArray()
   bei System.Linq.Buffer`1..ctor(IEnumerable`1 source)
   bei System.Linq.OrderedEnumerable`1.<GetEnumerator>d__17.MoveNext()
   bei LiveChartsCore.Kernel.Extensions.<SelectFirst>d__21`2.MoveNext()
   bei System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
   bei System.Linq.Enumerable.Any[TSource](IEnumerable`1 source)
   bei LiveChartsCore.Chart`1.<TooltipThrottlerUnlocked>b__151_1()
   bei Avalonia.Threading.DispatcherOperation.InvokeCore()
   bei Avalonia.Threading.DispatcherOperation.Execute()
   bei Avalonia.Threading.Dispatcher.ExecuteJob(DispatcherOperation job)
   bei Avalonia.Threading.Dispatcher.ExecuteJobsCore()
   bei Avalonia.Threading.Dispatcher.Signaled()
   bei Avalonia.Win32.Win32Platform.WndProc(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam)
   bei Avalonia.Win32.Interop.UnmanagedMethods.DispatchMessage(MSG& lpmsg)
   bei Avalonia.Win32.Win32DispatcherImpl.RunLoop(CancellationToken cancellationToken)
   bei Avalonia.Threading.DispatcherFrame.Run(IControlledDispatcherImpl impl)
   bei Avalonia.Threading.Dispatcher.PushFrame(DispatcherFrame frame)
   bei Avalonia.Threading.Dispatcher.MainLoop(CancellationToken cancellationToken)
   bei Avalonia.Controls.ApplicationLifetimes.ClassicDesktopStyleApplicationLifetime.Start(String[] args)
   bei Avalonia.ClassicDesktopStyleApplicationLifetimeExtensions.StartWithClassicDesktopLifetime(AppBuilder builder, String[] args, ShutdownMode shutdownMode)
   bei ChartTest.Desktop.Program.Main(String[] args) in D:\Dev\ChartTest\ChartTest.Desktop\Program.cs: Zeile13

To Reproduce See attached minimal example. ChartTest.zip

Expected behavior I would expect that there is some kinf of throtteling or debouncing to prevent crahes in case of frequent value updates.

Desktop (please complete the following information):

beto-rodriguez commented 1 year ago

Thanks for the sample! this issue is also related to #1048.

When the timer callback is called, it is not running in the UI thred, thus you can get this exception, please take a look here: https://github.com/beto-rodriguez/LiveCharts2/issues/225#issuecomment-1012488255 there is more explanation about this.

In this case you can fix it by forcing the update to happen in the UI thread:

private void Timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
  {
      Dispatcher.UIThread.Post(() =>
      {
          if (currentId >= 200000000)
          {
              return;
          }

          DateTime now = DateTime.UtcNow;

          IDisposable notificationSuspender = Values.SuspendNotifications();
          for (int i = 0; i < 100; i++)
          {
              Values.Add(new(now + TimeSpan.FromMilliseconds(i / 100.0), 1.0));
          }
          notificationSuspender.Dispose();
      });
  }
Developer-Alexander commented 1 year ago

Thank you. That's helpful. I would choose Alternative 2 (https://github.com/beto-rodriguez/LiveCharts2/issues/225#issuecomment-1012488255) as it remove some load from the UI thread.

However the longer the source collection get the less responsive the chart and the UI gets. Is there some kind of virtualisation, slicing or downsampling mechanism that helps keep the UI fluid?

I was trying to downsample the (frequently updating) input data depending on the currently visible part given by MinLimit and MaxLimit of the XAxis. But it did not work out yet. I would be very thankful for some tips.

beto-rodriguez commented 1 year ago

There is an experimental virtualization algorithm for the library.

https://github.com/beto-rodriguez/LiveCharts2/releases/tag/v2.0.0-beta.350

It helps a lot, soon it should be public for everyone.

Developer-Alexander commented 1 year ago

That sounds great! Please do not hesitate to release it very soon.

beto-rodriguez commented 1 year ago

I hope to do so!

For now, I will close this, thanks for the report.

Developer-Alexander commented 1 year ago

How do I know when the feature gets available?

Developer-Alexander commented 1 year ago

Any update on this?