gpt4 book ai didi

wpf - .NET 4 中是否有线程安全的 Observablecollection?

转载 作者:行者123 更新时间:2023-12-03 11:58:46 28 4
gpt4 key购买 nike

平台:WPF, .NET 4.0, C# 4.0
问题:在 Mainwindow.xaml 中,我有一个 ListBox 绑定(bind)到当前是 ObservableCollection 的 Customer 集合。
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/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com