簡體   English   中英

ObservableCollection 項的異步更新

[英]Asynchronous update to ObservableCollection items

我是多線程和 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;
            }

        }
    }

用戶檔案.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));
        }
    }
}

主窗口.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。

請參閱: .net 4.0的BindingOperations.EnableCollectionSynchronization()等效項

如果您使用的是WPF,則可以更新單個綁定項的屬性,並從后台線程中提升PropertyChanged。 WPF數據綁定機制(與WinForms等價物不同)檢測到這一點並為您編寫UI線程。 當然需要付出代價 - 使用自動機制,每個單獨的屬性更新都會導致編組事件,因此如果您要更改許多屬性,性能可能會受到影響,您應該考慮將UI編組自己作為單個批處理操作。

但是,您不允許操作集合(添加/刪除項目),因此如果您的RSS源包含要綁定到的嵌套集合,則需要提前將整個更新提升到UI線程。

對於這種應用程序,我通常使用BackgroundWorker並將ReportsProgress設置為True。 然后,您可以將每個調用的一個對象作為ReportProgress方法中的userState參數傳遞。 ProgressChanged事件將在UI線程上運行,因此您可以將對象添加到事件處理程序中的ObservableCollection。

否則,從后台線程更新屬性將起作用,但如果您正在過濾或排序ObservableCollection,則除非引發了某些集合更改通知事件,否則不會重新應用過濾器。

您可以通過查找集合中項目的索引(例如,通過將其報告為progresspercentage)並設置list.item(i)= e.userstate來重新應用過濾器和排序,即單獨替換列表中的項目在ProgressChanged事件中。 這樣,將保留綁定到集合的任何控件的SelectedItem,而過濾器和排序將遵循項中任何更改的值。

您可能希望查看.Net中的ConcurrentCollections命名空間。

http://msdn.microsoft.com/en-us/library/system.collections.concurrent.aspx

這是另一個可能有用的問題。

ObservableCollection和線程

我有一個類似的場景,並遇到這個“ObservableCollection不支持來自UI線程以外的線程的更新”,最后通過在Thomas Levesque的博客中引用這個AsyncObservableCollection工具解決了,我認為它可能對你有所幫助。

在其Update版本中,SynchronizationContext用於解決此問題。 您可以參考SynchronizationContextMSDN頁面

這是一個簡單的 observablecollection,在 AddRange 方法結束時通知,基於這篇文章https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/

它也是異步和跨線程可修改的,基於這篇文章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);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM