web-dev-qa-db-ja.com

ワーカースレッドを介してObservableCollectionを更新するにはどうすればよいですか?

ObservableCollection<A> a_collection;コレクションには「n」個のアイテムが含まれます。各アイテムAは次のようになります。

public class A : INotifyPropertyChanged
{

    public ObservableCollection<B> b_subcollection;
    Thread m_worker;
}

基本的に、それはすべて、WPFリストビューと、個別のリストビュー(2方向バインディング、propertychangedの更新など)で選択された項目のb_subcollectionを表示する詳細ビューコントロールに接続されています。スレッドの実装を開始すると、問題が明らかになりました。全体のアイデアは、a_collection全体がワーカースレッドを使用して「作業」を行い、それぞれのb_subcollectionsを更新して、GUIにリアルタイムで結果を表示することでした。

試してみると、DispatcherスレッドのみがObservableCollectionを変更できるという例外が発生し、作業が停止しました。

誰も問題を説明できますか、それを回避する方法はありますか?

乾杯

68
Maciek

技術的に問題は、バックグラウンドスレッドからObservableCollectionを更新することではありません。問題は、これを行うと、コレクションが変更を引き起こした同じスレッドでCollectionChangedイベントを発生させることです。つまり、コントロールはバックグラウンドスレッドから更新されます。

コントロールがバインドされているときにバックグラウンドスレッドからコレクションを作成するには、おそらくこれに対処するために独自のコレクションタイプを最初から作成する必要があります。しかし、あなたのためにうまくいくかもしれないより簡単なオプションがあります。

追加呼び出しをUIスレッドに投稿します。

public static void AddOnUI<T>(this ICollection<T> collection, T item) {
    Action<T> addMethod = collection.Add;
    Application.Current.Dispatcher.BeginInvoke( addMethod, item );
}

...

b_subcollection.AddOnUI(new B());

このメソッドは、すぐに(アイテムが実際にコレクションに追加される前に)戻り、UIスレッドでアイテムがコレクションに追加され、誰もが満足するはずです。

ただし、現実には、すべてのクロススレッドアクティビティのために、このソリューションは高負荷で機能しなくなる可能性があります。より効率的なソリューションでは、アイテムの束をまとめてUIスレッドに定期的に投稿し、各アイテムのスレッド間で呼び出しを行わないようにします。

BackgroundWorker クラスは、バックグラウンド操作中に ReportProgress メソッドを介して進行状況を報告できるパターンを実装します。進行状況は、ProgressChangedイベントを介してUIスレッドで報告されます。これはあなたのための別のオプションかもしれません。

65
Josh

.NET 4.5の新しいオプション

.NET 4.5以降では、コレクションへのアクセスを自動的に同期し、CollectionChangedイベントをUIスレッドにディスパッチする組み込みメカニズムがあります。この機能を有効にするには、 _BindingOperations.EnableCollectionSynchronization_Iスレッド内からを呼び出す必要があります。

EnableCollectionSynchronizationは2つのことを行います:

  1. 呼び出し元のスレッドを記憶し、データバインディングパイプラインにそのスレッドのCollectionChangedイベントをマーシャリングさせます。
  2. マーシャリングされたイベントが処理されるまでコレクションのロックを取得します。UIスレッドを実行しているイベントハンドラーは、コレクションがバックグラウンドスレッドから変更されている間、コレクションを読み取ろうとしません。

非常に重要なのは、これはすべてを処理するわけではありません:本質的にスレッドセーフではないコレクションへのスレッドセーフなアクセスを確保するためですコレクションが変更されようとしているときに、バックグラウンドスレッドから同じロックを取得することにより、フレームワークと協力する必要があります

したがって、正しい操作に必要な手順は次のとおりです。

1.使用するロックの種類を決定する

これにより、EnableCollectionSynchronizationのどのオーバーロードを使用する必要があるかが決まります。ほとんどの場合、単純なlockステートメントで十分であるため、 このオーバーロード が標準の選択ですが、派手な同期メカニズムを使用している場合は カスタムロックのサポートもあります

2.コレクションを作成し、同期を有効にします

選択したロックメカニズムに応じて、UIスレッドで適切なオーバーロードを呼び出します。標準のlockステートメントを使用する場合、ロックオブジェクトを引数として提供する必要があります。カスタム同期を使用する場合は、 CollectionSynchronizationCallback デリゲートとコンテキストオブジェクト(nullの場合があります)を指定する必要があります。呼び出されると、このデリゲートはカスタムロックを取得し、渡されたActionを呼び出し、戻る前にロックを解除する必要があります。

3.コレクションを変更する前にロックすることで協力する

また、コレクションを自分で変更しようとするときは、同じメカニズムを使用してコレクションをロックする必要があります。これは、単純なシナリオではEnableCollectionSynchronizationに渡される同じロックオブジェクトのlock()を使用して、またはカスタムシナリオでは同じカスタム同期メカニズムを使用して行います。

111
Jon

.NET 4.0では、次のワンライナーを使用できます。

.Add

Application.Current.Dispatcher.BeginInvoke(new Action(() => this.MyObservableCollection.Add(myItem)));

.Remove

Application.Current.Dispatcher.BeginInvoke(new Func<bool>(() => this.MyObservableCollection.Remove(myItem)));
15
WhileTrueSleep

後世のコレクション同期コード。これは、単純なロックメカニズムを使用してコレクションの同期を有​​効にします。 UIスレッドでコレクションの同期を有​​効にする必要があることに注意してください。

public class MainVm
{
    private ObservableCollection<MiniVm> _collectionOfObjects;
    private readonly object _collectionOfObjectsSync = new object();

    public MainVm()
    {

        _collectionOfObjects = new ObservableCollection<MiniVm>();
        // Collection Sync should be enabled from the UI thread. Rest of the collection access can be done on any thread
        Application.Current.Dispatcher.BeginInvoke(new Action(() => 
        { BindingOperations.EnableCollectionSynchronization(_collectionOfObjects, _collectionOfObjectsSync); }));
    }

    /// <summary>
    /// A different thread can access the collection through this method
    /// </summary>
    /// <param name="newMiniVm">The new mini vm to add to observable collection</param>
    private void AddMiniVm(MiniVm newMiniVm)
    {
        lock (_collectionOfObjectsSync)
        {
            _collectionOfObjects.Insert(0, newMiniVm);
        }
    }
}
2
LadderLogic