对ObservableCollection项的异步更新

Mar*_*tin 6 c# wpf asynchronous observablecollection

我是多线程和WPF的新手.

我有一个ObservableCollection<RSSFeed>,在app启动项目从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;
            }

        }
    }
Run Code Online (Sandbox Code Playgroud)

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));
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

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
    }

}
Run Code Online (Sandbox Code Playgroud)

谢谢.

KTC*_*TCO 5

使用.Net 4.5,您可以使用BindingOperations.EnableCollectionSynchronization向ObservableCollection添加对后台线程更新的支持.这适用于MVVM.

请参阅: .net 4.0的BindingOperations.EnableCollectionSynchronization()等效项


hba*_*rck 3

对于此类应用程序,我通常使用将 ReportsProgress 设置为 True 的 BackgroundWorker。然后,您可以为每次调用传递一个对象作为 ReportProgress 方法中的 userState 参数。ProgressChanged 事件将在 UI 线程上运行,因此您可以在事件处理程序中将对象添加到 ObservableCollection。

否则,从后台线程更新属性将起作用,但如果您正在过滤或排序 ObservableCollection,则不会重新应用过滤器,除非引发了某些集合更改通知事件。

您可以通过查找集合中项目的索引(例如,将其报告为进度百分比)并设置 list.item(i) = e.userstate,即自行替换列表中的项目,来重新应用过滤器和排序在 ProgressChanged 事件中。这样,绑定到集合的任何控件的 SelectedItem 将被保留,而筛选和排序将尊重项目中任何更改的值。