2012-05-09 15 views
6

Jestem nowy w wielowątkowości i WPF.Asynchroniczna aktualizacja elementów ObservableCollection

Mam ObservableCollection<RSSFeed>, przy elementach uruchamiania aplikacji są dodawane do tej kolekcji z wątku interfejsu użytkownika. Właściwości RSSFeed są związane z ListView WPF. Później chcę asynchronicznie zaktualizować każdy RSSFeed. Zastanawiam się więc nad implementacją czegoś takiego jak RSSFeed.FetchAsync() i podniesieniem właściwości PropertyChanged na jej zaktualizowanych właściwościach.

Wiem, że ObservableCollection nie obsługuje aktualizacji z wątków innych niż wątek interfejsu użytkownika, zgłasza wyjątek NotSupportedException. Ale ponieważ nie manipuluję obserwowalną kolekcją, ale raczej aktualizuję właściwości jej elementów, czy mogę oczekiwać, że to zadziała i zaktualizuje elementy ListView? Czy też może wyrzucił wyjątek z powodu PropertyChanged?

EDIT: Kod

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 
    } 

} 

Dzięki.

Odpowiedz

3

Dla tego rodzaju aplikacji, zazwyczaj użyć BackgroundWorker z ReportsProgress ustawiona na True. Następnie możesz przekazać jeden obiekt dla każdego połączenia jako parametr userState w metodzie ReportProgress. Zdarzenie ProgressChanged będzie działać w wątku interfejsu użytkownika, więc można dodać obiekt do obiektu ObservableCollection w module obsługi zdarzeń.

W przeciwnym razie będzie można zaktualizować właściwości z wątku w tle, ale jeśli filtrujesz lub sortujesz ObservableCollection, filtr nie zostanie ponownie zastosowany, o ile nie zostało zgłoszone zdarzenie związane ze zmianą kolekcji.

Możesz spowodować ponowne zastosowanie filtrów i sortów, znajdując indeks pozycji w kolekcji (np. Zgłaszając go jako progresspercentage) i ustawiając list.item (i) = e.userstate, np. Zastępując element w sama lista w zdarzeniu ProgressChanged.W ten sposób element SelectedItem wszystkich elementów sterujących związanych z kolekcją zostanie zachowany, a filtrowanie i sortowanie będzie respektować wszelkie zmienione wartości w elemencie.

3

Jeśli używasz WPF, możesz aktualizować właściwości poszczególnych powiązanych elementów i podnieść właściwości PropertyChanged z wątku tła. Mechanizmy wiążące dane WPF (w przeciwieństwie do odpowiednika WinForms) wykrywają to i prowadzą do wątku interfejsu użytkownika. Oczywiście wiąże się to z kosztami - użycie automatycznego mechanizmu powoduje, że każda pojedyncza aktualizacja właściwości spowoduje zdarzenie rozrządzania, więc jeśli zmieniasz wiele właściwości, wydajność może ucierpieć, i powinieneś rozważyć wykonanie układania interfejsu użytkownika jako jednej operacji wsadowej .

Nie można jednak manipulować kolekcjami (dodawać/usuwać elementy), więc jeśli twoje kanały RSS zawierają zagnieżdżone kolekcje, które chcesz powiązać, musisz wciągnąć całą aktualizację do wątku interfejsu z wyprzedzeniem.

+0

Dzięki. Mam kolekcję zagnieżdżoną w mojej klasie RSSFeed. Czy byłby to dobry souluting, gdyby RSSFeed.FetchAsync() podniósł wydarzenie po jego zakończeniu i zwrócił nową (zaktualizowaną) instancję RSSFeed poprzez EventArgs? Później zaktualizowałbym odpowiedni element w kolekcji z wątku interfejsu użytkownika. – Martin

+0

To możliwe rozwiązanie. Gdybyśmy mogli zobaczyć kod, mógłbym podać bardziej konkretną odpowiedź. –

+0

@malymato Zwykle metody asynchroniczne zapewniają wywołanie zwrotne po zakończeniu. Jakiego rodzaju wdrożenia używasz? –

0

Miałem podobny scenariusz i spotkał tę „ObservableCollection nie obsługuje aktualizacji z wątków innych niż wątku UI”, ale w końcu to rozwiązane poprzez odniesienie tej AsyncObservableCollection implement w blogu Thomas Levesque „s , Myślę, że może ci się przydać.

W wersji Update SynchronizationContext jest używany, aby rozwiązać ten problem. Można odwołać się do MSDN page of SynchronizationContext

Powiązane problemy