0

내 프로그램에서 백그라운드 작업자 스레드를 사용하여 파일을 엽니 다. 내 프로그램의 주요 구조는 데이터 바운드 TreeView입니다. 진행중인 파일 읽기 중에 동적 TreeView 노드가 파일에서 읽을 때 TreeView에 추가됩니다. 이 TreeView 노드는 UICollections (ObservableCollection<T>에서 상속 된 사용자 정의 클래스) 컨테이너에 바인딩됩니다. 이 유형의 CollectionViews이 배경 작업자 스레드에서 SourceCollections을 변경하지 않았는지 확인하기 위해 UICollection<T> 클래스를 생성했습니다. 저는 에있는 IsNotifying이라는 속성을 false으로 변경하여이 작업을 수행합니다.OnCollectionChanged 및 백그라운드 스레드 문제

UICollection<T> 클래스 : 그와

public class UICollection<T> : ObservableCollection<T> 
{ 
    public UICollection() 
    { 
     IsNotifying = true; 
    } 

    public UICollection(IEnumerable<T> source) 
    { 
     this.Load(source); 
    } 

    public bool IsNotifying { get; set; } 

    protected override void OnPropertyChanged(PropertyChangedEventArgs e) 
    { 
     if (IsNotifying) 
      base.OnPropertyChanged(e); 
    } 

    //Does not raise unless IsNotifying = true 
    protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e) 
    { 
     if (IsNotifying) 
      base.OnCollectionChanged(e); 
    } 

    //Used whenever I re-order a collection 
    public virtual void Load(IEnumerable<T> items) 
    { 
     if (items == null) 
      throw new ArgumentNullException("items"); 

     this.IsNotifying = false; 

     foreach (var item in items) 
      this.Add(item); 

     //ERROR created on this line because IsNotifying is always set to true 
     this.IsNotifying = true; 

     this.Refresh(); 
    } 

    public Action<T> OnSelectedItemChanged { get; set; } 

    public Func<T, bool> GetDefaultItem { get; set; } 

    public void Refresh() 
    { 
     OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset)); 
    } 
} 

나는 배경 작업자 스레드에서 UICollections을 추가해야 내 제어 구조,이 UICollection<T>을 구현하는 데 문제가 말했다되고.

명확성을 위해

, 내 프로그램 이동 다음과 같이 배경 작업자 스레드에서

  1. Is a file being opened?: YES -> go into Background worker thread

  2. : Do we need to create new UICollections?: YES -> go to method in UIThread that does so (필요에 따라 반복)

  3. 닫기 스레드.

이해 될 필요가 주요 개념은 UICollection.IsNotifying이 백그라운드 작업자 스레드가 열려있는 경우 false로 설정해야한다는 것입니다. 이미 알려진 컬렉션에 대해서는이 작업을 수행하는 데 아무런 문제가 없지만 동적 인 작업에 대해서는 문제가 발생합니다. 내 배경 작업자 스레드가하는 일의

샘플 : UIThread에서

private void openFile() 
{ 
    //Turn off CollectionChanged for known Collections 
    KnownCollections1.IsNotifying = false; 
    KnownCollections2.IsNotifying = false; //... and so on 

    //Do we need to create new collections? YES -> Call to AddCollection in UIThread 

    //Refresh known collections 
    App.Current.Dispatcher.BeginInvoke((Action)(() => KnownCollections1.Refresh())); 
    App.Current.Dispatcher.BeginInvoke((Action)(() => KnownCollections2.Refresh())); //... and so on 
    //If new collections exist find them and refresh them... 
} 

방법 TreeView에 컬렉션을 추가 : 그 모두와 함께

public void AddCollection(string DisplayName, int locationValue) 
{ 
    node.Children.Add(CreateLocationNode(displayName, locationValue)); //Add to parent node 

    for (int i = 0; i < node.Children.Count(); i++) 
    { 
     //make sure IsNotifying = false for newly added collection 
     if (node.Children[i].locationValue == locationValue) 
      node.Children[i].Children.IsNotifying = false; 
    } 

    //Order the collection based on numerical value 
    var ordered = node.Children.OrderBy(n => n.TreeView_LocValue).ToList(); 
    node.Children.Clear(); 
    node.Children.Load(ordered); //Pass to UICollection class -- *RUNS INTO ERROR* 
} 

, 두 가지 중 하나를 것입니다 일어난다. 내가 줄 this.IsNotifying = true;을 주석 처리하면, 예외는 OnCollectionChanged으로 날아갈 것이다. 스레드가 열려 있습니다. 줄을 그대로두면 컬렉션이보기에 반영되지 않습니다. 왜냐하면 OnCollectionChanged이 결코 나타나지 않아보기를 알리기 때문입니다. 이러한 오류가 발생하지 않고 이러한 모음을 만들 수 있도록하려면 어떻게해야합니까? 문제가 내 AddCollection() 함수 또는 UICollection<T> 클래스에있는 것 같습니다.

답변

2

올바르게 이해하면 동일한 컬렉션 (또는 '부모'컬렉션)이 UI의 항목 소스로 사용되는 동안 백그라운드 스레드에서 컬렉션 (또는 중첩 된 컬렉션)을 조작하고있는 것입니다. 변경 알림을 사용하지 않도록 설정해도 안전하지 않습니다. 사용자가 시작한 정렬, 트리 노드 확장, 가상화로 인한 컨테이너 재활용 등과 같이 컬렉션을 다시 쿼리 할 수있는 다른 것들이 있습니다. 다른 스레드에서 컬렉션을 업데이트하는 동안 이러한 현상이 발생하면 동작은 정의되지 않습니다.예를 들어, 다른 스레드에 대한 삽입으로 인해 기본 목록의 크기가 변경되고 결과적으로 null 또는 중복 항목이 읽힐 수 있으므로 동시에 컬렉션을 반복하도록 트리거 할 수 있습니다. 두 스레드간에 변경 가능한 데이터를 공유 할 때마다 읽기 및 쓰기를 동기화해야하며 읽기 작업을 수행하는 WPF 내부를 제어하지 않으므로 모든 종류의 동시 쓰기를 수행하는 것이 안전하다고 가정 할 수 없습니다. 여기에는 다른 스레드의 UI 바운드 컬렉션 내의 개체 수정이 포함됩니다.

배경 스레드에서 컬렉션을 조작해야하는 경우 원본의 스냅 샷을 찍은 다음 필요한 수정을 수행 한 다음 UI 스레드로 다시 정렬하여 변경 사항을 커밋합니다 (원본을 완전히 바꾸거나 컬렉션 지우기 및 다시 채우기). 이 기술을 사용하여 큰 데이터 세트가있는 그리드 뷰에서 배경 정렬, 그룹화 및 필터링을 안전하게 수행합니다. 하지만 이렇게하면 컬렉션에 포함 된 항목을 수정하지 않도록 조심하십시오. 컬렉션에 포함 된 항목은 UI에서 계속 참조 할 수 있습니다. 또한 UI 스레드에서 발생하는 백그라운드 변경을 무효화 할 수있는 변경 사항을 감지해야 할 수도 있습니다.이 경우 UI 스레드로 사용자를 다시 분류하고 다른 스냅 샷을 찍은 다음 변경 사항을 취소해야합니다. 또는 두 가지 변경 사항을 조정하는보다 정교한 방법을 생각해 내십시오.)

+0

안녕하세요. 마이크. 당신이 내 게시물을 아주 잘 이해하는 것처럼 들리네. 나는 그것이 길다는 것을 알고 있지만 가능한 한 명확히하려고 노력했다. 내 컬렉션을 변경하기 위해 UIThread로 이동할 때마다 백그라운드 스레드를 멈추도록 권장합니까? –

+0

UI 스레드가 참조하는 백그라운드 스레드의 컬렉션을 수정할 수 없다는 말입니다. 일반적으로 할 수있는 최선의 방법은 원본 컬렉션의 복사본을 만들고 복사본에서 배경을 수정 한 다음 완료되면 UI 스레드로 다시 정렬하여 원래 컬렉션을 수정 된 복사본으로 바꿉니다. UI 스레드와 백그라운드 스레드 모두에서 수정 작업을 수행하게되면 상황이 더욱 복잡해지며 두 가지 변경 사항을 조정할 방법이 필요하지만 UI 스레드에서 수행해야합니다 게다가. –

+0

감사합니다. @MikeStrobel. 추가해야 할 컬렉션을 모두 큐에 넣고 백그라운드 작업자 스레드를 닫은 후 추가하는 것이 더 쉬울까요? –