0

Using .NET 4.0, I have a Host application that parses a csv file, objectizes each row of data, and returns it as a type back to my client via a WCF callback. This part works fine. Where I start to run into trouble is when I try to add that type or collection of types to my ObservableCollection in the MainWindow.

So this...

 public class MyServiceCallback : IMyServiceCallback
    {
        //List<Location.LocationData> lastData = new List<Location.LocationData>();
        //Dictionary<string, Location.LocationData> lastData = new Dictionary<string,Location.LocationData>();
        //Network exposed Callback method which recieves Host/Client common data type
        //Note: Ought to be an interface and not a class type, but not needed for a small project
        public void OnCallback(Location.LocationData[] t)
        {
            //if(t.Where(x=>x.Frequency == lastData[
            //foreach (Location.LocationData d in t)
            //{
            //    lastData.Add(d.Frequency, d);
            //}
            //Call static method on MainWindow to pass the collection of LocationData to UI bound LocationList
            if(!(t.Length == 0))
            Client.MainWindow.SetLocationList(t.ToList());            
        }
    }

gets invoked from the WCF Host and SetLocation(t.ToList()) calls this...

public partial class MainWindow : Window
    {

private static MTObservableCollection<Location.LocationData> locationList = new MTObservableCollection<Location.LocationData>();

public static MTObservableCollection<Location.LocationData> LocationList
{
     get { return locationList; }
     set { locationList = value; }
} 

public static void SetLocationList(List<Location.LocationData> hostPushedLocationData)
        {
            //Clear previous location data
            LocationList.Clear();

            System.Threading.Thread.SpinWait(1);

            //Add the pushed data to the bound collection to be displayed
            hostPushedLocationData.ForEach(data => { LocationList.Add(data); System.Threading.Thread.SpinWait(1); });

        }
}

If I were using a plain ObservableCollection, this wouldn't work at all because I wouldn't be able to update the collection from the WCF thread.

If I extend ObservableCollection with this...

public class MTObservableCollection<T> : ObservableCollection<T>
    {
        public override event NotifyCollectionChangedEventHandler CollectionChanged;
        protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
            if (CollectionChanged != null)
                foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
                {
                    DispatcherObject dispObj = nh.Target as DispatcherObject;
                    if (dispObj != null)
                    {
                        Dispatcher dispatcher = dispObj.Dispatcher;
                        if (dispatcher != null && !dispatcher.CheckAccess())
                        {
                            dispatcher.BeginInvoke(
                                (Action)(() => nh.Invoke(this,
                                    new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
                                DispatcherPriority.DataBind);
                            continue;
                        }
                    }
                    nh.Invoke(this, e);
                }
        }

    }

which I found here: Where do I get a thread-safe CollectionView?

I intermittently get this error:

I am getting an exception using this version, but not when using the version provided by Jonathan. Does anyone have ideas why this is happening? Here is my InnerException: This exception was thrown because the generator for control 'System.Windows.Controls.DataGrid Items.Count:3' with name 'OrdersGrid' has received sequence of CollectionChanged events that do not agree with the current state of the Items collection. The following differences were detected: Accumulated count 2 is different from actual count 3. [Accumulated count is (Count at last Reset + #Adds - #Removes since last Reset)

which is mentioned in the linked post.

I also, more often, have gotten this error: Index was out of range.

If anybody could help or point me in the right direction on what I need to do to this collection extension to solve this problem, I would greatly appreciate it. Also, would this problem be mitigated if I used .NET 4.5 instead of 4.0?

Thanks a ton!

Community
  • 1
  • 1
  • thread-safe collections aren't prone to concurrency issues outside of the standard scope of add/remove etc. – Yosi Dahari Nov 04 '13 at 20:41
  • Are you saying I should use another collection as opposed to extending ObservableCollection? –  Nov 04 '13 at 20:43
  • I chose ObservableCollection knowing issues like this might exist, but used it because of the easy interface updating. –  Nov 04 '13 at 20:44

1 Answers1

0

You are deferring your collection change events by firing them via Dispatcher.BeginInvoke(). The collection may change several times between the time you schedule the event and when the event actually fires. Based on the exception message, it sounds like this is exactly what is happening.

Do not schedule change events to be raised later; they are meant to be raised immediately.

Mike Strobel
  • 25,075
  • 57
  • 69
  • Cool. I'll try this and let you know. –  Nov 04 '13 at 21:07
  • Note that once you start performing blocking Dispatcher calls to raise change events, you're probably worse off than you would have been if you just scheduled the changes to be performed on the UI thread. – Mike Strobel Nov 04 '13 at 21:12
  • Thinking about it again, it's likely impossible to update an observable collection from another thread safely. There is no way you can guarantee that the collection won't be modified while the UI thread is in the middle of updating its collection view. And even if you raise the change events with `Send` priority, you don't know what code will execute between the call to `Invoke()` and when you actually cross over to the dispatcher thread. – Mike Strobel Nov 04 '13 at 21:36
  • So, I tried invoke instead of begininvoke, and got the "An ItemsControl is inconsistent with its items source." exception immediately this time. So, what would be your suggestion on a fix? –  Nov 04 '13 at 21:58
  • See my previous comment. I don't think you can make this work reliably if you are updating the collection from another thread. Your best bet is to modify the collection on the UI thread. – Mike Strobel Nov 04 '13 at 22:13
  • Update: I gave up and started using .NET 4.5's BindingOperations.EnableCollectionSynchronization and seems to have totally fixed the problem. Fingers crossed! I've only run the app twice. Thanks for the input. –  Nov 04 '13 at 22:48
  • Note that will only work if you synchronize all accesses to the collection, including your updates from WCF. – Mike Strobel Nov 04 '13 at 22:59