Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Threading problem when adding items to an ObservableCollection

I'm updating an ObservableCollection of a WPF ViewModel in a WCF Data Service asynchronous query callback method:

ObservableCollection<Ent2> mymodcoll = new ObservableCollection<Ent2>();
 ...
query.BeginExecute(OnMyQueryComplete, query);
 ...
private void OnMyQueryComplete(IAsyncResult result)
    {
        ...
        var repcoll = query.EndExecute(result);

        if (mymodcoll.Any())
        {
            foreach (Ent c in repcoll)
            {
                var myItem = mymodcoll.Where(p => p.EntID == c.EntID).FirstOrDefault();
                if (myItem != null) 
                {
                    myItem.DateAndTime = c.DateAndTime; // here no problems
                    myItem.Description = c.Description;
                     ...
                }
                else
                {
                    mymodcoll.Add(new Ent2 //here I get a runtime error
                    {
                        EntID = c.EntID,
                        Description = c.Description,
                        DateAndTime = c.DateAndTime,
                        ...
                    });
                }
            }
        }
        else
        {
            foreach (Ent c in repcoll)
            {
                mymodcoll.Add(new Ent2 //here, on initial filling, there's no error
                {
                    EntID = c.EntID,
                    Description = c.Description,
                    DateAndTime = c.DateAndTime,
                    ...
                });
            }
        }
    }  

The problem is, when a query result collection contains an item which is not present in the target collection and I need to add this item, I get a runtime error: The calling thread cannot access this object because a different thread owns it. (I pointed out this line of code by a comment)

Nevertheless, if the target collection is empty (on initial filling) all items have been added without any problem. (This part of code I also pointed out by a comment). When an item just needs to update some of its fields, there are no problems as well, the item gets updated ok.

How could I fix this issue?

like image 750
rem Avatar asked Apr 25 '11 18:04

rem


1 Answers

First case: Here you a modifying an object in the collection, not the collection itself - thus the CollectionChanged event isn't fired.

Second case: here you are adding a new element into the collection from a different thread, the CollectionChanged event is fired. This event needs to be executed in the UI thread due to data binding.

I encountered that problem several times already, and the solution isn't pretty (if somebody has a better solution, please tell me!). You'll have to derive from ObservableCollection<T> and pass it a delegate to the BeginInvoke or Invoke method on the GUI thread's dispatcher.

Example:

public class SmartObservableCollection<T> : ObservableCollection<T>
{
    [DebuggerStepThrough]
    public SmartObservableCollection(Action<Action> dispatchingAction = null)
        : base()
    {
        iSuspendCollectionChangeNotification = false;
        if (dispatchingAction != null)
            iDispatchingAction = dispatchingAction;
        else
            iDispatchingAction = a => a();
    }

    private bool iSuspendCollectionChangeNotification;
    private Action<Action> iDispatchingAction;

    [DebuggerStepThrough]
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        if (!iSuspendCollectionChangeNotification)
        {
            using (IDisposable disposeable = this.BlockReentrancy())
            {
                iDispatchingAction(() =>
                {
                    base.OnCollectionChanged(e);
                });
            }
        }
    }
    [DebuggerStepThrough]
    public void SuspendCollectionChangeNotification()
    {
        iSuspendCollectionChangeNotification = true;
    }
    [DebuggerStepThrough]
    public void ResumeCollectionChangeNotification()
    {
        iSuspendCollectionChangeNotification = false;
    }


    [DebuggerStepThrough]
    public void AddRange(IEnumerable<T> items)
    {
        this.SuspendCollectionChangeNotification();
        try
        {
            foreach (var i in items)
            {
                base.InsertItem(base.Count, i);
            }
        }
        finally
        {
            this.ResumeCollectionChangeNotification();
            var arg = new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset);
            this.OnCollectionChanged(arg);
        }
    }


}
like image 192
Femaref Avatar answered Oct 20 '22 09:10

Femaref