ConcurrentModificationException when setting ScatterLineSeries.ItemsSource

10 posts, 0 answers
  1. Bill
    Bill avatar
    33 posts
    Member since:
    Nov 2017

    Posted 12 Dec 2018 Link to this post

    I am trying to set the ItemSource for a chart in a Task method, like this:

    Task ShowData() 
    {
      return Task.Factory.StartNew(() => {
        // do calculations here...
        LineSeries1.ItemsSource = _chartLines;
      }
    }

    However, when run, I get the following exception: Unhandled Exception: Java.Util.ConcurrentModificationException

    When I change this to a normal void method it works fine. Any pointers as to how to fix this?

    My Telerik.Xamarin.Android.Chart dll is v4.0.30319.

  2. Bill
    Bill avatar
    33 posts
    Member since:
    Nov 2017

    Posted 12 Dec 2018 in reply to Bill Link to this post

    This should go at the end of that code snippet I posted:

    , CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
  3. Lance | Team Lead - US DevTools Support
    Admin
    Lance | Team Lead - US DevTools Support avatar
    1048 posts

    Posted 12 Dec 2018 Link to this post

    Hi Jim,

    Without the rest of the code I can't give you a definitive answer. What happens when you marshal the task's work back to the UI thread when its done:

    Task ShowData()
    {
      return Task.Factory.StartNew(() => {
        // do calculations here...
     
        Device.BeginInvokeOnMainThread(()=> { LineSeries1.ItemsSource = _chartLines; };
         
      }
    }

    If this doesn't help, can you please reply with the rest of the code, I particularly need to see the following:

    - The chart definition (e.g. XAML)
    - The methods that invoke the task (e.g. is ShowData in OnAppearing?)
    - Any other code that will help me build the reprodicible (e.g. model class, view model class, etc)

    Regards,
    Lance | Tech Support Engineer, Sr.
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  4. Bill
    Bill avatar
    33 posts
    Member since:
    Nov 2017

    Posted 12 Dec 2018 in reply to Lance | Team Lead - US DevTools Support Link to this post

    Here's my chart xaml:

    <chart:RadCartesianChart x:Name="ChartResults" VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand" Margin="1,1,1,1" Grid.Row="0" Grid.Column="0">
        <chart:RadCartesianChart.HorizontalAxis>
            <chart:NumericalAxis x:Name="HorizontalAxis" />
        </chart:RadCartesianChart.HorizontalAxis>
        <chart:RadCartesianChart.VerticalAxis>
            <chart:NumericalAxis x:Name="YAxis2" LabelFormat="0.##" />
        </chart:RadCartesianChart.VerticalAxis>
        <chart:RadCartesianChart.Grid>
            <chart:CartesianChartGrid MajorLinesVisibility="XY" />
        </chart:RadCartesianChart.Grid>
        <chart:RadCartesianChart.Series>
            <chart:ScatterLineSeries x:Name="LineSeries1" Stroke="CornflowerBlue" StrokeThickness="1" />
        </chart:RadCartesianChart.Series>
    </chart:RadCartesianChart>

     

    ShowData is called by a Timer. The Timer is initialised at the end of the OnAppearing method:

    ShowWaveformDelegate showWaveform = DoShowWaveform
    MyEvent.Callback = showWaveform
    Globals.MyTimer = new Timer { Interval = 1000 };
    Globals.MyTimer.Elapsed += OnTimedEvent;
    Globals.MyTimer.Start();

     

    private static void OnTimedEvent(object source, ElapsedEventArgs e
    {
      Globals.MyTimer.Stop();
      MyEvent.Run();
    }

     

    private async void DoShowWaveform(Data data)
    {
      await ShowWaveform(data);
    }

     

    The idea here is to update the chart every second, but when the chart is being updated, stop the timer, then restart the timers after it has been updated.

  5. Bill
    Bill avatar
    33 posts
    Member since:
    Nov 2017

    Posted 12 Dec 2018 Link to this post

    MyEvent looks like this:

    public static Delegate Callback { get; set; }
     
    public static async void Run()
    {
        try
            {
                // collect data
     
                Callback?.DynamicInvoke(data);
            }
            catch (Exception e)
            {
                // ignored
            }
        }
    }
  6. Lance | Team Lead - US DevTools Support
    Admin
    Lance | Team Lead - US DevTools Support avatar
    1048 posts

    Posted 12 Dec 2018 Link to this post

    Hi Bill,

    Unfortunately, the code is not complete enough for me to recreate the exception as there are missing pieces and infrastructure. Regardless, I suspect the reason for the error is the code is multiple threads are trying to update the series simultaneously.

    It's recommended that you use Device.StartTimer in Xamarin.Forms as it will use the proper clock for the device. Here's a good discussion on StackOverflow about System Timer vs Device.StartTimer.


    Demo

    You could use the PropertyChanged event of the series to know when the last time the ItemsSource was set before starting the 1 second timer again.

    // subscribe to Propertychanged
    LineSeries1.PropertyChanged += LineSeries1_PropertyChanged;
     
     
    // the event handler that will fire off another 1 second timer
    private void LineSeries1_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        // this will be true is the ItemsSource was reset
        if (e.PropertyName == nameof(ScatterLineSeries.ItemsSource))
        {
            // Get the next data in 1 second
            Device.StartTimer(TimeSpan.FromMilliseconds(1000), OnTick);
        }
    }

    To stop each timer, you return false (returning True continues the timer)

    private bool OnTick()
    {
        Task.Run(async () =>
        {
            // Get Data From sensors
            await Task.Delay(200);
     
            // Set the ItemsSource
            Device.BeginInvokeOnMainThread(() =>
            {
                LineSeries1.ItemsSource = _lineSeriesData;
            });
        });
     
        // Stop the timer
        return false;
    }

    With this approach you have a reliable order of operations. Here's the debug output from my attached demo to verify everything is occurringas intended:

    [0:] OnTick
    [0:] Reading Sensor Data
    [0:] Setting ItemsSource
    [0:] OnTick
    [0:] Reading Sensor Data
    [0:] Setting ItemsSource
    [0:] OnTick
    [0:] Reading Sensor Data
    [0:] Setting ItemsSource
    [0:] OnTick
    [0:] Reading Sensor Data
    [0:] Setting ItemsSource


    Regards,
    Lance | Tech Support Engineer, Sr.
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  7. Bill
    Bill avatar
    33 posts
    Member since:
    Nov 2017

    Posted 13 Dec 2018 Link to this post

    Thanks for the example. It works, but the timer isn't being retriggered after the LineSeries1.ItemSource is set for the second time. Here's my debug output:

    [0:] OnTick
    [0:] stop timer
    [0:] do reading
    [0:] show waveform
    [0:] start timer
    [0:] OnTick
    [0:] stop timer
    [0:] do reading
    [0:] show waveform

    My code is the same as your, except I do await ShowWaveform(data) in the OnTick method, and I set LineSeries1 in my ShowWaveform method, like this:

    Device.BeginInvokeOnMainThread(() =>
    {
      LineSeries1.ItemsSource?.Clear();
      LineSeries1.ItemsSource = _chartLines;
    });

    I also tried omitting the Clear line, but still no joy.

     

     

     

  8. Lance | Team Lead - US DevTools Support
    Admin
    Lance | Team Lead - US DevTools Support avatar
    1048 posts

    Posted 13 Dec 2018 Link to this post

    Hello Bill,

    You don't need to call Clear on the ItemsSource because you're resetting the value in the next line. Since ItemsSource is a DependencyProperty, setting it replaces the previous value in the control.

    I can confirm that the LineSeries's ItemsSource is working as expected (as demonstrated in my attached demo). I've reattached it here as a runnable solution so you can see this first-hand. When the page launches, the ItemsSource is updated continuously.

    I'm unable to directly diagnose the issue you're having, but as you suspect it might be due to using await in the tick instead of my recommended approach (you would do the async call where I have Task.Delay).


    Further Assistance using Task

    Telerik Forums specifically covers the Telerik controls and their APIs, the problem you're having isn't related to the Chart's APIs. For example, if you swapped out the Chart for a plain Xamarin ListView, you'd have the same problem.

    If you'd like assistance in redesigning or debugging the architecture, I recommend posting in StackOverflow or the Xamarin Forums. This isn't an uncommon threading issue and you'll be able to get assistance from the Xamarin and .NET community who've previously built solutions for this scenario.


    Professional Services

    If you do get completely stuck, and are unable to find any solution, we have partners who are specialized  work on development problems like this and help you build out the app. You can contact them here: Professional Services - Architecture Review or Professional Services - Custom App Dev


    Regards,
    Lance | Tech Support Engineer, Sr.
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
  9. Bill
    Bill avatar
    33 posts
    Member since:
    Nov 2017

    Posted 14 Dec 2018 Link to this post

    I'm not so sure that this is a general async issue. The first time the timer is triggered, the following line:

    LineSeries1.ItemsSource = _chartLines;

    Correctly causes the LineSeries1_PropertyChanged event to be triggered. The second time the ItemsSource is set, the LineSeries1_PropertyChanged event is not triggered.

  10. Lance | Team Lead - US DevTools Support
    Admin
    Lance | Team Lead - US DevTools Support avatar
    1048 posts

    Posted 14 Dec 2018 Link to this post

    Hello Bill,

    This is what I'm referring to. The fact that PropertyChanged event is not firing, means that the logic in the task is not actually setting ItemsSource.

    If you can isolate this in a separate runnable project, send that project to us in a support ticket (you can create one here). I'll ask the development team to to take a look and offer suggestions on how to restructure.

    Regards,
    Lance | Tech Support Engineer, Sr.
    Progress Telerik
    Do you want to have your say when we set our development plans? Do you want to know when a feature you care about is added or when a bug fixed? Explore the Telerik Feedback Portal and vote to affect the priority of the items
Back to Top