programing

스레드 세이프 Collection View는 어디서 구할 수 있나요?

mbctv 2023. 4. 21. 21:18
반응형

스레드 세이프 Collection View는 어디서 구할 수 있나요?

백그라운드 스레드에서 비즈니스 오브젝트 컬렉션을 업데이트하면 다음 오류 메시지가 나타납니다.

이 유형의 CollectionView는 Dispatcher 스레드와 다른 스레드에서 SourceCollection으로의 변경을 지원하지 않습니다.

그래, 말이 되네.그러나 Collection View의 어떤 버전이 여러 스레드를 지원하는지, 그리고 객체가 어떻게 이 스레드를 사용할 수 있는지에 대한 질문도 있습니다.

용도:

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

다음은 Jonathan이 발견한 구현 개선 사항입니다.첫 번째로 모든 이벤트핸들러가 같은(UI) 디스패처 상에 있다고 가정하지 않고 연관된 디스패처 상에서 각 이벤트핸들러를 실행합니다.두 번째로 Begin을 사용합니다.호출하여 디스패처가 사용 가능해질 때까지 처리를 계속할 수 있도록 합니다.이것에 의해, 백그라운드 스레드가 각 스레드간의 처리로 많은 갱신을 실시하는 상황에서는, 솔루션의 고속화를 도모할 수 있습니다.아마도 더 중요한 것은 Invoke를 기다리는 동안 블로킹으로 인한 문제를 해결한다는 것입니다(예를 들어 Concurency Mode에서 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);
            }
    }
}

Begin을 사용하고 있기 때문에호출하면 핸들러가 호출되기 전에 변경 알림이 취소될 수 있습니다.이로 인해 일반적으로 이벤트 인수가 목록의 새로운(변경된) 상태와 대조될 때 "Index was out of range." 예외가 느려집니다.이를 방지하기 위해 지연된 모든 이벤트는 Reset 이벤트로 대체됩니다.이로 인해 과도한 그리기 작업이 발생할 수 있습니다.

Bea Stollnitz의 이 게시물은 오류 메시지와 왜 이 메시지가 그대로 표현되었는지 설명합니다.

편집: Bea 블로그에서

유감스럽게도 이 코드에서는 예외가 발생합니다.「 Not Supported 」예외 – 이런 유형의 CollectionView는 Dispatcher 스레드와 다른 스레드에서 SourceCollection으로의 변경을 지원하지 않습니다."이 에러 메세지는, 사용하고 있는 Collection View가 크로스 스레드 변경을 서포트하고 있지 않은 경우는, 그 변경을 서포트하고 있는 것을 찾아낼 필요가 있다고 생각하도록 유도합니다.이 에러 메시지는 조금 오해를 불러일으킵니다.즉, 개봉 즉시 제공되는 CollectionView는 크로스 스레드 컬렉션 변경을 지원하지 않습니다.아니요, 안타깝게도 현재 오류 메시지를 수정할 수 없습니다. 매우 많이 잠겨 있습니다.

하나 찾았어요.

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

다음 항목도 참조할 수 있습니다.BindingOperations.EnableCollectionSynchronization.

로의 업그레이드를 참조해 주세요.NET 4.5: ItemsControl이 해당 항목 소스와 일치하지 않습니다.

죄송합니다. 댓글을 추가할 수 없지만 이 모든 것은 잘못되었습니다.

ObservableCollection은 스레드 안전이 아닙니다.디스패처 문제뿐만 아니라 (msdn에서) 스레드 세이프가 전혀 아닙니다.

이 유형의 공용 정적(Visual Basic에서 공유) 구성원은 스레드 안전합니다.인스턴스 멤버가 스레드 세이프가 보장되지 않습니다.

http://msdn.microsoft.com/en-us/library/ms668604(v=vs.110).aspx 를 참조해 주세요.

Begin을 호출할 때도 문제가 있습니다."Reset" 작업으로 호출합니다."Reset"은 핸들러가 컬렉션 자체를 확인해야 하는 유일한 작업입니다.시작하는 경우「리셋」을 기동해, 곧바로 개시합니다.몇 가지 "추가" 작업을 호출하면 핸들러가 이미 업데이트된 컬렉션을 포함한 "재설정"을 수락하고 다음 "추가" 작업을 실행하면 혼란이 발생합니다.

이것이 효과적인 나의 구현입니다.사실 Begin을 삭제하려고 합니다.호출:

빠른 성능과 스레드 세이프 관측 가능한 수집

다음과 같이 수집 동기화를 활성화하면 컬렉션에 대한 크로스 스레드 변경을 wpf에서 관리할 수 있습니다.

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

이를 통해 WPF는 UI 스레드에서 컬렉션이 변경될 수 있으므로 UI 변경 내용을 적절한 스레드로 다시 수집해야 함을 알 수 있습니다.

잠금 개체가 없는 경우 동기화 콜백을 제공하기 위한 오버로드도 있습니다.

WPF UI Control을 정기적으로 업데이트하고 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;
            }
        }   

    }

시험:

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

 //Code

}));

그들 중 아무도, 그냥 디스패처를 사용해.BeginInvoke

여기 제가 구글 검색과 약간의 수정을 거쳐 만든 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

VB 버전에서는 작은 오류가 발생했습니다.대체:

Dim obj As DispatcherObject = invocation.Target

타고

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

언급URL : https://stackoverflow.com/questions/2137769/where-do-i-get-a-thread-safe-collectionview

반응형