2012-05-09 7 views
6

Ich bin neu in Multithreading und WPF.Asynchrone Aktualisierung auf ObservableCollection-Elemente

Ich habe eine ObservableCollection<RSSFeed>, bei App Start Elemente zu dieser Sammlung von UI-Thread hinzugefügt werden. Eigenschaften von RSSFeed sind an WPF ListView gebunden. Später möchte ich jeden RSSFeed asynchron aktualisieren. Also ich überlege, etwas wie RSSFeed.FetchAsync() zu implementieren und PropertyChanged auf seinen aktualisierten Eigenschaften zu erhöhen.

Ich weiß, dass ObservableCollection keine Updates von anderen Threads als dem UI-Thread unterstützt, sondern NotSupportedException. Aber da ich die ObservableCollection selbst nicht manipuliere, sondern Eigenschaften für ihre Elemente aktualisiere, kann ich damit rechnen, dass dies funktioniert und die ListView-Elemente aktualisiert werden? Oder würde es wegen PropertyChanged sowieso eine Ausnahme auslösen?

Edit: Code

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 
    } 

} 

Danke.

Antwort

3

Für diese Art von Anwendung, verwende ich in der Regel eine Background mit ReportsProgress auf True gesetzt. Dann können Sie für jeden Aufruf ein Objekt als Parameter "userState" in der ReportProgress-Methode übergeben. Das ProgressChanged-Ereignis wird im UI-Thread ausgeführt, sodass Sie das Objekt der ObservableCollection im Ereignishandler hinzufügen können.

Andernfalls funktioniert das Aktualisieren der Eigenschaften von einem Hintergrundthread, aber wenn Sie die ObservableCollection filtern oder sortieren, wird der Filter nicht erneut angewendet, es sei denn, ein Sammlungsänderungsbenachrichtigungsereignis wurde ausgelöst.

Sie können veranlassen, dass Filter und Sortierungen erneut angewendet werden, indem Sie den Index des Elements in der Sammlung suchen (z. B. indem Sie den Fortschritt melden) und list.item (i) = e.userstate festlegen, dh das Element ersetzen die Liste selbst im ProgressChanged-Ereignis.Auf diese Weise wird das SelectedItem aller Steuerelemente, die an die Auflistung gebunden sind, beibehalten, während beim Filtern und Sortieren geänderte Werte im Element berücksichtigt werden.

3

Wenn Sie WPF verwenden, können Sie die Eigenschaften einzelner gebundener Elemente aktualisieren und PropertyChanged von einem Hintergrundthread aus aufrufen. Die WPF-Datenbindungsmechanismen erkennen dies (im Gegensatz zu der entsprechenden WinForms-Entsprechung) und ordnen sie dem UI-Thread für Sie zu. Es gibt natürlich auch Kosten. Bei Verwendung des automatischen Mechanismus führt jedes einzelne Propertyupdate zu einem Marshalling-Ereignis. Wenn Sie also viele Eigenschaften ändern, kann die Leistung darunter leiden, und Sie sollten überlegen, ob Sie die UI-Rangierung als einzelne Stapeloperation ausführen .

Sie sind jedoch nicht berechtigt, Sammlungen zu bearbeiten (Elemente hinzufügen/entfernen). Wenn Ihre RSS-Feeds also verschachtelte Sammlungen enthalten, die Sie binden möchten, müssen Sie das gesamte Update vorab auf den UI-Thread hissen.

+0

Danke beziehen. Ich habe eine verschachtelte Sammlung in meiner RSSFeed-Klasse. Wäre es eine gute Lösung, wenn RSSFeed.FetchAsync() bei seiner Beendigung ein Ereignis ausgelöst und eine neue (aktualisierte) RSSFeed-Instanz über EventArgs zurückgegeben hat? Später würde ich das entsprechende Element in der Sammlung vom UI-Thread aktualisieren. – Martin

+0

Das ist eine mögliche Lösung. Wenn wir etwas Code sehen könnten, könnte ich Ihnen eine konkretere Antwort geben. –

+0

@malymato normalerweise Async Methoden bieten einen Rückruf, wenn sie abgeschlossen sind. Welche Art von Implementierung verwenden Sie? –

0

hatte ich ein ähnliches Szenario und traf diese "ObservableCollection nicht Updates von Threads außer dem UI-Thread unterstützen", schließlich wurde es unter Bezugnahme diesen AsyncObservableCollection implement in Thomas Levesque ‚s Blog gelöst Ich denke, dass es für dich hilfreich sein kann.

In seiner Update-Version wird SynchronizationContext verwendet, um dieses Problem zu lösen. Sie können sich auf die MSDN page of SynchronizationContext