对可观察集合项的异步更新
本文关键字:异步 更新 集合 观察 | 更新日期: 2023-09-27 18:33:50
我是多线程和WPF的新手。
我有一个ObservableCollection<RSSFeed>
,在应用程序启动项从 UI 线程添加到此集合中。RSSFeed 的属性绑定到 WPF ListView。稍后,我想异步更新每个 RSSFeed。所以我正在考虑实现类似 RSSFeed.FetchAsync()
的东西并在其更新的属性上提高 PropertyChanged。
我知道 ObservableCollection 不支持来自 UI 线程以外的线程的更新,它会抛出 NotSupportedException。但是,由于我不是在操作 ObservableCollection 本身,而是更新其项的属性,因此我是否可以期望它起作用并查看更新的 ListView 项?或者它会因为属性更改而抛出异常吗?
编辑:代码
RSSFeed.cs
public class RSSFeed
{
public String Title { get; set; }
public String Summary { get; set; }
public String Uri { get; set; }
public String Encoding { get; set; }
public List<FeedItem> Posts { get; set; }
public bool FetchedSuccessfully { get; protected set; }
public RSSFeed()
{
Posts = new List<FeedItem>();
}
public RSSFeed(String uri)
{
Posts = new List<FeedItem>();
Uri = uri;
Fetch();
}
public void FetchAsync()
{
// call Fetch asynchronously
}
public void Fetch()
{
if (Uri != "")
{
try
{
MyWebClient client = new MyWebClient();
String str = client.DownloadString(Uri);
str = Regex.Replace(str, "<!--.*?-->", String.Empty, RegexOptions.Singleline);
FeedXmlReader reader = new FeedXmlReader();
RSSFeed feed = reader.Load(str, new Uri(Uri));
if (feed.Title != null)
Title = feed.Title;
if (feed.Encoding != null)
Encoding = feed.Encoding;
if (feed.Summary != null)
Summary = feed.Summary;
if (feed.Posts != null)
Posts = feed.Posts;
FetchedSuccessfully = true;
}
catch
{
FetchedSuccessfully = false;
}
}
}
用户配置文件.cs
public class UserProfile : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public event CollectionChangeEventHandler CollectionChanged;
private ObservableCollection<RSSFeed> feeds;
public ObservableCollection<RSSFeed> Feeds
{
get { return feeds; }
set { feeds = value; OnPropertyChanged("Feeds"); }
}
public UserProfile()
{
feeds = new ObservableCollection<RSSFeed>();
}
protected void OnPropertyChanged(string name)
{
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
handler(this, new PropertyChangedEventArgs(name));
}
}
protected void OnCollectionChanged(RSSFeed feed)
{
CollectionChangeEventHandler handler = CollectionChanged;
if (handler != null)
{
handler(this, new CollectionChangeEventArgs(CollectionChangeAction.Add, feed));
}
}
}
MainWindow.xaml.cs
public partial class MainWindow : Window, INotifyPropertyChanged
{
// My ListView is bound to this
// ItemsSource="{Binding Posts}
public List<FeedItem> Posts
{
get
{
if (listBoxChannels.SelectedItem != null)
return ((RSSFeed)listBoxChannels.SelectedItem).Posts;
else
return null;
}
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
// here I load cached feeds
// called from UI thread
// now I want to update the feeds
// since network operations are involved,
// I need to do this asynchronously to prevent blocking the UI thread
}
}
谢谢。
Net 4.5,可以使用 BindingOperations.EnableCollectionSynchronization 向 ObservableCollection 添加对后台线程更新的支持。这适用于 MVVM。
看:BindingOperations.EnableCollectionSynchronization(( 等效于 .net 4.0
对于这种应用程序,我通常使用将 ReportsProgress 设置为 True 的 BackgroundWorker 。然后,可以将每个调用的一个对象作为 ReportProgress 方法中的 userState 参数传递。ProgressChanged 事件将在 UI 线程上运行,因此可以将对象添加到事件处理程序中的 ObservableCollection。
否则,从后台线程更新属性将起作用,但如果要对 ObservableCollection 进行筛选或排序,则除非引发某些集合更改通知事件,否则不会重新应用筛选器。
您可以通过在集合中查找项目的索引(例如,将其报告为 progresspercent(并设置 list.item(i( = e.userstate(即在 ProgressChanged 事件中替换列表中的项目本身(来重新应用过滤器和排序。这样,绑定到集合的任何控件的 SelectedItem 都将保留,而筛选和排序将遵循项中的任何更改值。
如果使用 WPF,则可以更新单个绑定项的属性,并从后台线程引发 PropertyChanged。 WPF 数据绑定机制(与 WinForms 等效机制不同(检测到这一点,并为你封送到 UI 线程。 当然,这样做是有代价的 - 使用自动机制,每个单独的属性更新都会导致编组事件,因此如果您要更改大量属性,性能可能会受到影响,您应该考虑将 UI 封送自己作为单个批处理操作。
但是,您不能操作集合(添加/删除项目(,因此如果您的 RSS 源包含要绑定到的嵌套集合,则需要提前将整个更新提升到 UI 线程。
你可能想要查看 .Net 中的 ConcurrentCollections 命名空间。
http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx
这是另一个可能也有帮助的问题。
可观察集合和线程
我有一个类似的场景,遇到了这个"ObservableCollection不支持来自UI线程以外的线程的更新",最后通过引用这个AsyncObservableCollection实现来解决在Thomas Levesque的博客中,我认为它可能对您有所帮助。
在其更新版本中,SynchronizationContext 用于解决此问题。您可以参考 SynchronizationContext 的 MSDN 页面
简单的可观察集合,基于这篇文章 https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/在 AddRange 方法结束时发出通知
它也是异步的,并且可以跨线程修改,基于这篇文章 https://thomaslevesque.com/2009/04/17/wpf-binding-to-an-asynchronous-collection/
public class ConcurrentObservableCollection<T> : ObservableCollection<T>
{
private SynchronizationContext _synchronizationContext = SynchronizationContext.Current;
private bool _suppressNotification = false;
public ConcurrentObservableCollection()
: base()
{
}
public ConcurrentObservableCollection(IEnumerable<T> list)
: base(list)
{
}
public void AddRange(IEnumerable<T> collection)
{
if (collection != null)
{
_suppressNotification = true;
foreach (var item in collection)
{
this.Add(item);
}
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
public void RemoveRange(IEnumerable<T> collection)
{
if (collection != null)
{
_suppressNotification = true;
foreach (var item in collection)
{
this.Remove(item);
}
_suppressNotification = false;
OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
}
}
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
{
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the CollectionChanged event on the current thread
RaiseCollectionChanged(e);
}
else
{
// Raises the CollectionChanged event on the creator thread
_synchronizationContext.Send(RaiseCollectionChanged, e);
}
}
protected override void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (SynchronizationContext.Current == _synchronizationContext)
{
// Execute the PropertyChanged event on the current thread
RaisePropertyChanged(e);
}
else
{
// Raises the PropertyChanged event on the creator thread
_synchronizationContext.Send(RaisePropertyChanged, e);
}
}
private void RaiseCollectionChanged(object param)
{
// We are in the creator thread, call the base implementation directly
if (!_suppressNotification)
base.OnCollectionChanged((NotifyCollectionChangedEventArgs)param);
}
private void RaisePropertyChanged(object param)
{
// We are in the creator thread, call the base implementation directly
base.OnPropertyChanged((PropertyChangedEventArgs)param);
}
}