我是多线程和WPF的新手。
我有一个ObservableCollection<RSSFeed>
,在应用启动项中,从UI线程添加到此集合中。 RSSFeed的属性绑定到WPF ListView。后来,我想异步更新每个RSSFeed。所以我正在考虑实现RSSFeed.FetchAsync()
之类的东西,并在其更新的属性上提升PropertyChanged。
我知道ObservableCollection不支持来自UI线程以外的线程的更新,它会抛出NotSupportedException。但是因为我没有操纵ObservableCollection本身而是更新其项目的属性,我可以期待这个工作并看到ListView项目更新吗?或者由于PropertyChanged它会抛出异常吗?
编辑代码
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;
}
}
}
UserProfile.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
}
}
感谢。
答案 0 :(得分:5)
使用.Net 4.5,您可以使用BindingOperations.EnableCollectionSynchronization向ObservableCollection添加对后台线程更新的支持。这适用于MVVM。
请参阅: BindingOperations.EnableCollectionSynchronization() equivalent for .net 4.0
答案 1 :(得分:3)
如果您使用的是WPF,则可以更新各个绑定项的属性,并从后台线程中提升PropertyChanged。 WPF数据绑定机制(与WinForms等价物不同)检测到这一点并为您编写UI线程。当然需要付出代价 - 使用自动机制,每个单独的属性更新都会导致编组事件,因此如果您要更改许多属性,性能可能会受到影响,您应该考虑将UI编组自己作为单个批处理操作
但是不允许操作集合(添加/删除项目),因此如果您的RSS源包含要绑定的嵌套集合,则需要提前将整个更新提升到UI线程。
答案 2 :(得分:3)
对于这种应用程序,我通常使用BackgroundWorker并将ReportsProgress设置为True。然后,您可以将每个调用的一个对象作为ReportProgress方法中的userState参数传递。 ProgressChanged事件将在UI线程上运行,因此您可以将对象添加到事件处理程序中的ObservableCollection。
否则,从后台线程更新属性将起作用,但如果您正在过滤或排序ObservableCollection,则除非已引发某些集合更改通知事件,否则不会重新应用过滤器。
您可以通过查找集合中项目的索引(例如,通过将其报告为progresspercentage)并设置list.item(i)= e.userstate来重新应用过滤器和排序,即替换项目中的项目。在ProgressChanged事件中单独列出。这样,将保留绑定到集合的任何控件的SelectedItem,而过滤器和排序将尊重项目中任何更改的值。
答案 3 :(得分:1)
您可能希望查看.Net中的ConcurrentCollections命名空间。
http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx
这是另一个可能有用的问题。
答案 4 :(得分:0)
我有一个类似的场景并且遇到了这个“ObservableCollection不支持UI线程以外的线程的更新”,最后通过在Thomas Levesque的博客中引用这个AsyncObservableCollection implement来解决,我想它可能对你有所帮助。
在其Update版本中,SynchronizationContext用于解决此问题。您可以参考MSDN page of SynchronizationContext