This is a migrated thread and some comments may be shown as answers.

Error Binding to Observable Collection

3 Answers 206 Views
Chart
This is a migrated thread and some comments may be shown as answers.
Charles
Top achievements
Rank 1
Charles asked on 05 Mar 2020, 06:09 PM
Hey Gang,
I'm attempting to bind a graph to an observable collection, and I'm getting 
"Collection was modified; enumeration operation may not execute."

Ok - I'm 99% sure what is going on - the collection (which is of immutable objects BTW) gets items added by a time in a remote DLL - every 1/2 second or so

I'm guess that is the issue.  Even if I could pause the remote operation, what is the preferred way to get the bound collection onto the graph's thread, as we never know when the collection will update

My current code - the basic  chart is in the XAML (and named dataGraph)  (oh, this is all UWP)

If I comment out the line for the ItemsSource, I'm all good

   LineSeries lineSeries = new LineSeries();
  lineSeries.Stroke = new SolidColorBrush(Colors.Orange);
  lineSeries.StrokeThickness = 2;
  lineSeries.VerticalAxis = new LinearAxis();

  lineSeries.ItemsSource = theData; //this is the  ObservableCollection<DataItem>
  lineSeries.CategoryBinding = new PropertyNameDataPointBinding() { PropertyName = "TimeUTC" };
  lineSeries.ValueBinding = new PropertyNameDataPointBinding() { PropertyName = "Value" };

  dataGraph.HorizontalAxis = new DateTimeContinuousAxis() { LabelFormat = "HH:mm:ss" };
  dataGraph.Series.Add(lineSeries);

3 Answers, 1 is accepted

Sort by
0
Lance | Manager Technical Support
Telerik team
answered on 05 Mar 2020, 07:40 PM

Hi Charles,

That error means something modified the collection while it was being enumerated over.

For example

1. a foreach loop will exhibit the exception

foreach(var item in items)
{
    item.Remove(item);
    // or
    items.Add(new item);
}

2. Or two different operations are trying to change the collection at the same time.

The solution for #1 would be to use a for loop instead.

for (int i = 0; i < items.Count - 1; i++)
{
    items.Remove(items[i]);
    // or
    items.InsertAt(index, itemtoadd);
}

the solution for #2 is to make sure you're using Tasks to properly ensure that the new items coming in do not get added while the last batch is still updating the collection.

Options

Indeed, your suspicion is most likely correct. If you're updating the collection every 500 milliseconds, you will want to take care with two things:

- You're doing the collection changes on the UI thread

- You're not modifying the collection too quickly (this is probably the cause, the last modification is still occurring when the next one comes in). Make sure that you batch these changes in queued tasks.

Conceptual Example

I have successfully updated charts with 50ms intervals, that's not been an issue before. The issue is how and when the new data is added. The best way to do this is to clip from the beginning and add to the end.

public async Task UpdateChartDataAsync()
{
    var itemsToAdd = await getNewDataAsync();

	Dispatcher.BeginInvokeAsync(NormalPriority, ()=>
	{
		// Trim the beginning
		if(theData.Count > MaximumItemsIWantToSee)
		{
			var numberToRemoveFromBeginning = itemsToAdd.Count;
			for (int i = 0; i <= numberToRemoveFromBeginning; i++)
			{
					theData.RemoveAt(i);
			}
		}
			
		// Add the new items to the end
		foreach(var item in itemsToAdd)
		{
			 theData.AddRange(item);
		}
	}
}

 

For more help with scheduling these tasks, I recommend posting this on StackOverflow. the UI part of things isn't particularly relevant, so you can abstract that way and just say "chart" or "list" to decrease the complexity of the question.

Regards,
Lance | Team Lead - US DevTools Support
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
0
Charles
Top achievements
Rank 1
answered on 06 Mar 2020, 01:53 PM

Thanks.
I ended up doing something somewhat different that well, MOSTLY works
What I ended up doing is in the form that is passed _theData  I created another Observable collection _localData

I created an event for _theData_CollectionChanged, and in there, I marshal over to the UI thread, and then
ObservableCollection<DataItem> _localData = new ObservableCollection<DataItem>(_theData);

Then I 
dataGraph.Series[0].ItemsSource = _localData;

As this all occurs on the UI thread, no problem - except for one MINOR one - as we are rebinding the source each update, the entire graph redraws!!

If I pre bind using 
dataGraph.Series[0].ItemsSource = _localData;

at graph setup, and just
_localData.Add (myDataItem) - I get a "no data" UNLESS I also then 
dataGraph.Series[0].ItemsSource = _localData;

again

Not sure what I'm doing wrong.  That rebind is causing quite a delay on the redraw (the one good thing is that the collections are 'add only', and I can add the records one collection to the other


0
Lance | Manager Technical Support
Telerik team
answered on 06 Mar 2020, 05:39 PM

Hello Charles,

There is no need to reset the _localData collection every time. This defeats the purpose of using an ObservableCollection in the first place.

For example, when new data gets added to theData, go ahead and update _localData accordingly using the Add/Remove methods

private void TheData_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
    if (e.Action == NotifyCollectionChangedAction.Add)
    {
        foreach (var item in e.NewItems)
        {
            _localData.Add((ChartDataPoint)item);
        }
    }

    if (e.Action == NotifyCollectionChangedAction.Remove)
    {
        foreach (var item in e.OldItems)
        {
            _localData.Remove((ChartDataPoint)item);
        }
    }

    if (e.Action == NotifyCollectionChangedAction.Reset)
    {
        _localData.Clear();
    }
}

Though I don't see why you can't just use "theData" for the ItemsSource. 

you can try this in the attached example:

If this is the cause of the original exception, then there is a problem inside the fetching logic that isn't properly updating theData. What might be happening is that you're dispatching to the UI thread and it is updating the collection in the wrong order. Handing off the action to the dispatcher does not ensure it is going to be executed in a specific order.

Further Investigation

Unfortunately, there isn't much Telerik UI for UWP can help with this setup as this issue isn't specific to the Chart, it's an issue with the data side of things and not the chart series itself (i.e. if you swapped the chart with a plain ListView or GridView, you will have the same problem).

This is why I recommend hopping over to StackOverflow to get help with the concurrency issue using a simple ListView implementation. The Telerik forums are to get help with the Telerik controls themselves, but are unequipped to assist with WinRT or .NET framework problems.

You can tag it with many options like C#, .NET, WinRT and UWP. Once a solution is implemented for the ListView.ItemsSource, you can use it with the series.ItemsSource.

Regards,
Lance | Team Lead - US DevTools Support
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
Tags
Chart
Asked by
Charles
Top achievements
Rank 1
Answers by
Lance | Manager Technical Support
Telerik team
Charles
Top achievements
Rank 1
Share this question
or