Проблема многопоточности при добавлении элементов в коллекцию ObservableCollection - PullRequest
7 голосов
/ 25 апреля 2011

Я обновляю ObservableCollection WPF ViewModel в методе обратного вызова асинхронного запроса службы данных WCF:

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,
                    ...
                });
            }
        }
    }  

Проблема заключается в том, что коллекция результатов запроса содержит элемент, которого нет вцелевая коллекция, и мне нужно добавить этот элемент, я получаю ошибку времени выполнения: Вызывающий поток не может получить доступ к этому объекту, потому что другой поток владеет им. (я указал на эту строку кодапо комментарию)

Тем не менее, если целевая коллекция пуста (при начальном заполнении), все элементы были добавлены без проблем.(На эту часть кода я также указал комментарий).Когда элементу просто нужно обновить некоторые из его полей, проблем также нет, элемент обновляется нормально.

Как я могу решить эту проблему?

1 Ответ

3 голосов
/ 25 апреля 2011

Первый случай: здесь вы модифицируете объект в коллекции, а не саму коллекцию - таким образом, событие CollectionChanged не запускается.

Второй случай: здесь вы добавляете новый элемент в коллекцию из другого потока, вызывается событие CollectionChanged. Это событие необходимо выполнить в потоке пользовательского интерфейса из-за привязки данных.

Я уже сталкивался с этой проблемой уже несколько раз, и решение не изящное (если у кого-то есть лучшее решение, пожалуйста, скажите мне!). Вам придется извлечь из ObservableCollection<T> и передать его делегату методу BeginInvoke или Invoke в диспетчере потока GUI.

Пример:

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);
        }
    }


}
...