webdevqa.jp.net

スレッドセーフのCollectionViewはどこで入手できますか?

バックグラウンドスレッドでビジネスオブジェクトのコレクションを更新すると、次のエラーメッセージが表示されます。

このタイプのCollectionViewは、Dispatcherスレッドとは異なるスレッドからのSourceCollectionへの変更をサポートしていません。

わかりました。しかし、それはまた、CollectionViewのどのバージョンが複数のスレッドをサポートしているか、そしてどのようにオブジェクトにそれを使用させるのかという疑問を投げかけています。

67
Jonathan Allen

以下は、ジョナサンによって発見された実装の改善です。まず、すべてが同じ(UI)ディスパッチャーにあると想定するのではなく、関連付けられているディスパッチャーで各イベントハンドラーを実行します。次に、BeginInvokeを使用して、ディスパッチャが使用可能になるのを待つ間、処理を続行できるようにします。これにより、バックグラウンドスレッドが各スレッド間の処理で多くの更新を行っている状況で、ソリューションがはるかに高速になります。おそらくもっと重要なことは、Invokeの待機中のブロックに起因する問題を克服することです(たとえば、ConcurrencyMode.SingleでWCFを使用しているときにデッドロックが発生する可能性があります)。

public class MTObservableCollection<T> : ObservableCollection<T>
{
    public override event NotifyCollectionChangedEventHandler CollectionChanged;
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
    {
        NotifyCollectionChangedEventHandler CollectionChanged = this.CollectionChanged;
        if (CollectionChanged != null)
            foreach (NotifyCollectionChangedEventHandler nh in CollectionChanged.GetInvocationList())
            {
                DispatcherObject dispObj = nh.Target as DispatcherObject;
                if (dispObj != null)
                {
                    Dispatcher dispatcher = dispObj.Dispatcher;
                    if (dispatcher != null && !dispatcher.CheckAccess())
                    {
                        dispatcher.BeginInvoke(
                            (Action)(() => nh.Invoke(this,
                                new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))),
                            DispatcherPriority.DataBind);
                        continue;
                    }
                }
                nh.Invoke(this, e);
            }
    }
}

BeginInvokeを使用しているため、ハンドラーが呼び出される前に、通知されている変更が取り消される可能性があります。これは通常、「インデックスが範囲外でした」という結果になります。イベント引数がリストの新しい(変更された)状態に対してチェックされるときにスローされる例外。これを回避するために、遅延イベントはすべてリセットイベントに置き換えられます。これは、場合によっては過度の再描画を引き起こす可能性があります。

64
Nathan Phillips

使用する:

System.Windows.Application.Current.Dispatcher.Invoke(
    System.Windows.Threading.DispatcherPriority.Normal,
    (Action)delegate() 
    {
         // Your Action Code
    });
87
luke

This Bea Stollnitzによる投稿では、thatエラーメッセージと、そのとおりの理由が説明されています。

EDIT:Beaのブログより

残念ながら、このコードの結果は例外になります。「NotSupportedException –このタイプのCollectionViewは、Dispatcherスレッドとは異なるスレッドからのSourceCollectionへの変更をサポートしていません。」 usingはスレッド間の変更をサポートしていないため、サポートしているものを見つける必要があります。さて、このエラーメッセージは少し誤解を招く可能性があります。最初から提供しているCollectionViewは、スレッド間のコレクションの変更をサポートしていません。いいえ、残念ながら、この時点でエラーメッセージを修正することはできません。非常にロックされています。

17

一つ見つかった。

public class MTObservableCollection<T> : ObservableCollection<T>
{
   public override event NotifyCollectionChangedEventHandler CollectionChanged;
   protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
   {
      var eh = CollectionChanged;
      if (eh != null)
      {
         Dispatcher dispatcher = (from NotifyCollectionChangedEventHandler nh in eh.GetInvocationList()
                 let dpo = nh.Target as DispatcherObject
                 where dpo != null
                 select dpo.Dispatcher).FirstOrDefault();

        if (dispatcher != null && dispatcher.CheckAccess() == false)
        {
           dispatcher.Invoke(DispatcherPriority.DataBind, (Action)(() => OnCollectionChanged(e)));
        }
        else
        {
           foreach (NotifyCollectionChangedEventHandler nh in eh.GetInvocationList())
              nh.Invoke(this, e);
        }
     }
  }
}

http://www.julmar.com/blog/mark/2009/04/01/AddingToAnObservableCollectionFromABackgroundThread.aspx

7
Jonathan Allen
3
Richard

次のようにコレクションの同期を有​​効にすると、wpfでコレクションへのスレッド間の変更を管理できます。

BindingOperations.EnableCollectionSynchronization(collection, syncLock);
listBox.ItemsSource = collection;

これにより、UIスレッドからコレクションを変更して、UIの変更を適切なスレッドにマーシャリングする必要があることがWPFに通知されます。

また、ロックオブジェクトがない場合に同期コールバックを提供するオーバーロードもあります。

2
Hamish

申し訳ありませんが、コメントを追加することはできませんが、これはすべて間違っています。

ObservableCollectionはスレッドセーフではありません。このディスパッチャの問題だけでなく、スレッドセーフではありません(msdnから):

この型のpublic static(Visual BasicではShared)メンバーは、スレッドセーフです。インスタンスメンバーは、スレッドセーフであるとは限りません。

こちらをご覧ください http://msdn.Microsoft.com/en-us/library/ms668604(v = vs.110).aspx

「リセット」アクションでBeginInvokeを呼び出すときにも問題があります。 「リセット」は、ハンドラーがコレクション自体を確認する唯一のアクションです。 BeginInvokeで「Reset」を実行し、すぐにBeginInvokeでいくつかの「Add」アクションを実行すると、ハンドラーは既に更新されたコレクションで「Reset」を受け入れ、次の「Add」で混乱が発生します。

これが機能する私の実装です。実際、BeginInvokeを削除することを考えています。

高速でスレッドセーフなオブザーバブルコレクション

2
norekhov

WPF UIコントロールを定期的に更新し、同時にUIを使用する場合は、DispatcherTimerを使用できます。

[〜#〜] xaml [〜#〜]

<Grid>
        <DataGrid AutoGenerateColumns="True" Height="200" HorizontalAlignment="Left" Name="dgDownloads" VerticalAlignment="Top" Width="548" />
        <Label Content="" Height="28" HorizontalAlignment="Left" Margin="0,221,0,0" Name="lblFileCouner" VerticalAlignment="Top" Width="173" />
</Grid>

C#

 public partial class DownloadStats : Window
    {
        private MainWindow _parent;

        DispatcherTimer timer = new DispatcherTimer();

        ObservableCollection<FileView> fileViewList = new ObservableCollection<FileView>();

        public DownloadStats(MainWindow parent)
        {
            InitializeComponent();

            _parent = parent;
            Owner = parent;

            timer.Interval = new TimeSpan(0, 0, 1);
            timer.Tick += new EventHandler(timer_Tick);
            timer.Start();
        }

        void timer_Tick(object sender, EventArgs e)
        {
            dgDownloads.ItemsSource = null;
            fileViewList.Clear();

            if (_parent.contentManagerWorkArea.Count > 0)
            {
                foreach (var item in _parent.contentManagerWorkArea)
                {
                    FileView nf = item.Value.FileView;

                    fileViewList.Add(nf);
                }
            }

            if (fileViewList.Count > 0)
            {
                lblFileCouner.Content = fileViewList.Count;
                dgDownloads.ItemsSource = fileViewList;
            }
        }   

    }
1
Developer

VB私がいくつかのグーグルとわずかな改造の後に作ったバージョンです。私のために動作します。

  Imports System.Collections.ObjectModel
  Imports System.Collections.Specialized
  Imports System.ComponentModel
  Imports System.Reflection
  Imports System.Windows.Threading

  'from: http://stackoverflow.com/questions/2137769/where-do-i-get-a-thread-safe-collectionview
  Public Class ThreadSafeObservableCollection(Of T)
    Inherits ObservableCollection(Of T)

    'from: http://geekswithblogs.net/NewThingsILearned/archive/2008/01/16/listcollectionviewcollectionview-doesnt-support-notifycollectionchanged-with-multiple-items.aspx
    Protected Overrides Sub OnCollectionChanged(ByVal e As System.Collections.Specialized.NotifyCollectionChangedEventArgs)
      Dim doit As Boolean = False

      doit = (e.NewItems IsNot Nothing) AndAlso (e.NewItems.Count > 0)
      doit = doit OrElse ((e.OldItems IsNot Nothing) AndAlso (e.OldItems.Count > 0))

      If (doit) Then
        Dim handler As NotifyCollectionChangedEventHandler = GetType(ObservableCollection(Of T)).GetField("CollectionChanged", BindingFlags.Instance Or BindingFlags.NonPublic).GetValue(Me)
        If (handler Is Nothing) Then
          Return
        End If

        For Each invocation As NotifyCollectionChangedEventHandler In handler.GetInvocationList
          Dim obj As DispatcherObject = invocation.Target

          If (obj IsNot Nothing) Then
            Dim disp As Dispatcher = obj.Dispatcher
            If (disp IsNot Nothing AndAlso Not (disp.CheckAccess())) Then
              disp.BeginInvoke(
                Sub()
                  invocation.Invoke(Me, New NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset))
                End Sub, DispatcherPriority.DataBind)
              Continue For
            End If
          End If

          invocation.Invoke(Me, e)
        Next
      End If
    End Sub
  End Class
0
Peter pete

VBバージョンの小さな間違い。

Dim obj As DispatcherObject = invocation.Target

沿って

Dim obj As DispatcherObject = TryCast(invocation.Target, DispatcherObject)
0
Patrice

それらのどれも、ただDispatcher.BeginInvokeを使用してください

0
Ana Betts

これを試して:

this.Dispatcher.Invoke(DispatcherPriority.Background, new Action(
() =>
{

 //Code

}));
0