6

マルチスレッドとWPFは初めてです。

私は、ObservableCollection<RSSFeed>アプリの起動時にアイテムがUIスレッドからこのコレクションに追加されています。RSSFeedのプロパティは、WPFListViewにバインドされます。後で、各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
    }

}

ありがとう。

4

6 に答える 6

5

.Net 4.5では、BindingOperations.EnableCollectionSynchronizationを使用して、バックグラウンドスレッド更新のサポートをObservableCollectionに追加できます。これはMVVMでうまく機能します。

参照: .net 4.0と同等のBindingOperations.EnableCollectionSynchronization()

于 2013-09-15T19:52:23.633 に答える
3

WPFを使用している場合は、個々のバインドされたアイテムのプロパティを更新し、バックグラウンドスレッドからPropertyChangedを発生させることができます。WPFデータバインディングメカニズム(同等のWinFormsとは異なり)はこれを検出し、UIスレッドにマーシャリングします。もちろん、これにはコストがかかります。自動メカニズムを使用すると、個々のプロパティの更新ごとにマーシャリングイベントが発生するため、多くのプロパティを変更する場合はパフォーマンスが低下する可能性があるため、UIマーシャリングを単一のバッチ操作として実行することを検討する必要があります。 。

ただし、コレクションの操作(アイテムの追加/削除)は許可されていないため、RSSフィードにバインドするネストされたコレクションが含まれている場合は、事前に更新全体をUIスレッドに上げる必要があります。

于 2012-05-09T15:08:58.257 に答える
3

この種のアプリケーションでは、通常、ReportsProgressをTrueに設定したBackgroundWorkerを使用します。次に、ReportProgressメソッドのuserStateパラメーターとして、呼び出しごとに1つのオブジェクトを渡すことができます。ProgressChangedイベントはUIスレッドで実行されるため、イベントハンドラーのObservableCollectionにオブジェクトを追加できます。

それ以外の場合は、バックグラウンドスレッドからのプロパティの更新は機能しますが、ObservableCollectionをフィルター処理または並べ替える場合、コレクション変更通知イベントが発生しない限り、フィルターは再適用されません。

コレクション内のアイテムのインデックスを検索し(たとえば、progresspercentageとしてレポートすることにより)、list.item(i)= e.userstateを設定することにより、つまりリスト内のアイテムを単独で置き換えることにより、フィルターと並べ替えを再適用させることができます。 ProgressChangedイベントで。このように、コレクションにバインドされたコントロールのSelectedItemは保持されますが、フィルターと並べ替えはアイテム内の変更された値を尊重します。

于 2012-05-09T16:38:45.250 に答える
1

.NetのConcurrentCollections名前空間を確認することをお勧めします。

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

これも役立つかもしれない別の質問です。

ObservableCollectionとスレッド化

于 2012-05-09T16:37:44.930 に答える
1

同様のシナリオがあり、この「ObservableCollectionはUIスレッド以外のスレッドからの更新をサポートしていません」に遭遇しました。最終的に、Thomas LevesqueのブログでこのAsyncObservableCollection実装を参照することで解決しました。これは、役立つと思います。

Updateバージョンでは、SynchronizationContextを使用してこの問題を解決します。あなたはSynchronizationContextのMSDNページを参照することができます

于 2013-12-04T02:29:45.803 に答える
0

これは、この投稿https://peteohanlon.wordpress.com/2008/10/22/bulk-loading-in-observablecollection/に基づいて、AddRangeメソッドの最後に通知する単純な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);
    }
}
于 2021-11-01T14:58:02.183 に答える