我正在WCF数据服务异步查询回调方法中更新WPF ViewModel的ObservableCollection:
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,
...
});
}
}
}
问题是,当查询结果集合包含目标集合中不存在的项目并且我需要添加此项目时,我收到运行时错误: 调用线程无法访问此对象因为一个不同的线程拥有它。 (我通过评论指出了这行代码)
尽管如此,如果目标集合为空(在初始填充时),所有项目都已添加,没有任何问题。 (这部分代码我也通过评论指出)。当一个项目只需要更新它的某些字段时,也没有问题,该项目会更新确定。
我该如何解决这个问题?
答案 0 :(得分:3)
第一种情况:在这里修改集合中的对象,而不是集合本身 - 因此CollectionChanged
事件不会被触发。
第二种情况:在这里,您将从另一个线程向集合中添加一个新元素,触发CollectionChanged
事件。由于数据绑定,此事件需要在UI线程中执行。
我已经好几次遇到过这个问题了,解决方案并不漂亮(如果有人有更好的解决方案,请告诉我!)。您必须从ObservableCollection<T>
派生,并将委托传递给GUI线程调度程序上的BeginInvoke
或Invoke
方法。
示例:
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);
}
}
}