- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
平台:WPF, .NET 4.0, C# 4.0
问题:在 Mainwindow.xaml 中,我有一个 ListBox 绑定(bind)到当前是 ObservableCollection
ObservableCollection<Customer> c = new ObservableCollection<Customer>();
该集合可以通过多个来源进行更新,例如 FileSystem、WebService 等。
为了允许并行加载客户,我创建了一个辅助类
public class CustomerManager(ref ObsevableCollection<Customer> cust)
它在内部为每个客户源生成一个新任务(来自并行扩展库),并将一个新的客户实例添加到客户集合对象(通过 ref 传递给它的 ctor)。
问题是 ObservableCollection< T> (或与此相关的任何集合)不能从 UI 线程以外的调用中使用,并且遇到异常:
“NotSupportedException – 这种类型的 CollectionView 不支持从不同于 Dispatcher 线程的线程更改其 SourceCollection。”
我尝试使用
System.Collections.Concurrent.ConcurrentBag<Customer>
集合,但它没有实现 INotifyCollectionChanged 接口(interface)。因此我的 WPF UI 不会自动更新。
那么,是否有一个集合类既实现了属性/集合更改通知,又允许来自其他非 UI 线程的调用?
通过我最初的 bing/谷歌搜索,没有提供开箱即用的功能。
编辑:我创建了自己的集合,它继承自 ConcurrentBag< Customer > 并且还实现了 INotifyCollectionChanged 接口(interface)。但令我惊讶的是,即使在单独的任务中调用它之后,WPF UI 也会挂起,直到任务完成。
任务不是应该并行执行并且不会阻塞UI线程 ?
提前感谢您的任何建议。
最佳答案
有两种可能的方法。第一个是从并发集合继承并添加 INotifyCollectionChanged 功能,第二个是从实现 INotifyCollectionChanged 的集合继承并添加并发支持。我认为将 INotifyCollectionChanged 支持添加到并发集合中要容易和安全得多。我的建议如下。
它看起来很长,但大多数方法只是调用内部并发集合,就好像调用者直接使用它一样。从集合中添加或删除的少数方法注入(inject)了对私有(private)方法的调用,该方法在构造时提供的调度程序上引发通知事件,从而允许类是线程安全的,但确保所有通知都在同一个线程上引发时间。
using System;
using System.Collections;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using System.Security.Permissions;
using System.Windows.Threading;
namespace Collections
{
/// <summary>
/// Concurrent collection that emits change notifications on a dispatcher thread
/// </summary>
/// <typeparam name="T">The type of objects in the collection</typeparam>
[Serializable]
[ComVisible(false)]
[HostProtection(SecurityAction.LinkDemand, Synchronization = true, ExternalThreading = true)]
public class ObservableConcurrentBag<T> : IProducerConsumerCollection<T>,
IEnumerable<T>, ICollection, IEnumerable
{
/// <summary>
/// The dispatcher on which event notifications will be raised
/// </summary>
private readonly Dispatcher dispatcher;
/// <summary>
/// The internal concurrent bag used for the 'heavy lifting' of the collection implementation
/// </summary>
private readonly ConcurrentBag<T> internalBag;
/// <summary>
/// Initializes a new instance of the ConcurrentBag<T> class that will raise <see cref="INotifyCollectionChanged"/> events
/// on the specified dispatcher
/// </summary>
public ObservableConcurrentBag(Dispatcher dispatcher)
{
this.dispatcher = dispatcher;
this.internalBag = new ConcurrentBag<T>();
}
/// <summary>
/// Initializes a new instance of the ConcurrentBag<T> class that contains elements copied from the specified collection
/// that will raise <see cref="INotifyCollectionChanged"/> events on the specified dispatcher
/// </summary>
public ObservableConcurrentBag(Dispatcher dispatcher, IEnumerable<T> collection)
{
this.dispatcher = dispatcher;
this.internalBag = new ConcurrentBag<T>(collection);
}
/// <summary>
/// Occurs when the collection changes
/// </summary>
public event NotifyCollectionChangedEventHandler CollectionChanged;
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event on the <see cref="dispatcher"/>
/// </summary>
private void RaiseCollectionChangedEventOnDispatcher(NotifyCollectionChangedEventArgs e)
{
this.dispatcher.BeginInvoke(new Action<NotifyCollectionChangedEventArgs>(this.RaiseCollectionChangedEvent), e);
}
/// <summary>
/// Raises the <see cref="CollectionChanged"/> event
/// </summary>
/// <remarks>
/// This method must only be raised on the dispatcher - use <see cref="RaiseCollectionChangedEventOnDispatcher" />
/// to do this.
/// </remarks>
private void RaiseCollectionChangedEvent(NotifyCollectionChangedEventArgs e)
{
this.CollectionChanged(this, e);
}
#region Members that pass through to the internal concurrent bag but also raise change notifications
bool IProducerConsumerCollection<T>.TryAdd(T item)
{
bool result = ((IProducerConsumerCollection<T>)this.internalBag).TryAdd(item);
if (result)
{
this.RaiseCollectionChangedEventOnDispatcher(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
return result;
}
public void Add(T item)
{
this.internalBag.Add(item);
this.RaiseCollectionChangedEventOnDispatcher(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item));
}
public bool TryTake(out T item)
{
bool result = this.TryTake(out item);
if (result)
{
this.RaiseCollectionChangedEventOnDispatcher(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove, item));
}
return result;
}
#endregion
#region Members that pass through directly to the internal concurrent bag
public int Count
{
get
{
return this.internalBag.Count;
}
}
public bool IsEmpty
{
get
{
return this.internalBag.IsEmpty;
}
}
bool ICollection.IsSynchronized
{
get
{
return ((ICollection)this.internalBag).IsSynchronized;
}
}
object ICollection.SyncRoot
{
get
{
return ((ICollection)this.internalBag).SyncRoot;
}
}
IEnumerator<T> IEnumerable<T>.GetEnumerator()
{
return ((IEnumerable<T>)this.internalBag).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return ((IEnumerable)this.internalBag).GetEnumerator();
}
public T[] ToArray()
{
return this.internalBag.ToArray();
}
void IProducerConsumerCollection<T>.CopyTo(T[] array, int index)
{
((IProducerConsumerCollection<T>)this.internalBag).CopyTo(array, index);
}
void ICollection.CopyTo(Array array, int index)
{
((ICollection)this.internalBag).CopyTo(array, index);
}
#endregion
}
}
关于wpf - .NET 4 中是否有线程安全的 Observablecollection?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/3899940/
我是一名优秀的程序员,十分优秀!