나는있어 ObservableCollection<A> a_collection;
컬렉션은 ‘N’항목이 포함되어 있습니다. 각 항목 A는 다음과 같습니다.
public class A : INotifyPropertyChanged
{
public ObservableCollection<B> b_subcollection;
Thread m_worker;
}
기본적으로 WPF 목록보기 + b_subcollection
선택한 항목을 별도의 목록보기 (양방향 바인딩, 속성 변경시 업데이트 등)에 표시하는 세부 정보보기 컨트롤에 모두 연결되어 있습니다.
스레딩을 구현하기 시작했을 때 문제가 나타났습니다. 전체 아이디어는 a_collection
작업자 스레드를 전체적으로 사용하여 “작업”한 다음 각각을 업데이트하고 b_subcollections
GUI가 결과를 실시간으로 표시하도록 하는 것이 었 습니다.
시도했을 때 Dispatcher 스레드 만 ObservableCollection을 수정할 수 있으며 작업이 중단되었다는 예외가 발생했습니다.
누구든지 문제를 설명하고 해결 방법을 설명 할 수 있습니까?
답변
기술적으로 문제는 백그라운드 스레드에서 ObservableCollection을 업데이트하는 것이 아닙니다. 문제는 그렇게 할 때 컬렉션이 변경을 일으킨 동일한 스레드에서 CollectionChanged 이벤트를 발생 시킨다는 것입니다. 즉, 컨트롤이 백그라운드 스레드에서 업데이트되고 있음을 의미합니다.
컨트롤이 바인딩 된 동안 백그라운드 스레드에서 컬렉션을 채우려면이 문제를 해결하기 위해 처음부터 고유 한 컬렉션 형식을 만들어야합니다. 하지만 더 간단한 옵션이 있습니다.
Add 호출을 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 스레드에보고됩니다. 이것은 당신을위한 또 다른 옵션 일 수 있습니다.
답변
.NET 4.5의 새로운 옵션
.NET 4.5부터 컬렉션에 대한 액세스를 자동으로 동기화하고 CollectionChanged
이벤트를 UI 스레드로 전달하는 기본 제공 메커니즘이 있습니다. 이 기능을 활성화하려면 UI 스레드 내 에서 호출해야합니다 .BindingOperations.EnableCollectionSynchronization
EnableCollectionSynchronization
두 가지를 수행합니다.
- 호출 된 스레드를 기억하고 데이터 바인딩 파이프 라인이
CollectionChanged
해당 스레드에서 이벤트 를 마샬링 하도록합니다. - 마샬링 된 이벤트가 처리 될 때까지 컬렉션에 대한 잠금을 획득하여 UI 스레드를 실행하는 이벤트 처리기가 백그라운드 스레드에서 수정되는 동안 컬렉션을 읽지 않도록합니다.
매우 중요한 것은 이것이 모든 것을 처리하지는 않는다는 것입니다 . 본질적으로 스레드로부터 안전하지 않은 컬렉션에 대한 스레드로부터 안전한 액세스를 보장 하려면 컬렉션이 수정 되려고 할 때 백그라운드 스레드에서 동일한 잠금을 획득하여 프레임 워크와 협력 해야합니다.
따라서 올바른 작동에 필요한 단계는 다음과 같습니다.
1. 사용할 잠금 유형을 결정합니다.
이것은 어떤 과부하를 EnableCollectionSynchronization
사용 해야하는지 결정 합니다. 대부분의 경우 간단한 lock
명령문으로 충분하므로이 오버로드 가 표준 선택이지만 멋진 동기화 메커니즘을 사용하는 경우 사용자 지정 잠금 도 지원됩니다 .
2. 컬렉션 생성 및 동기화 활성화
선택한 잠금 메커니즘 에 따라 UI 스레드 에서 적절한 오버로드 를 호출합니다 . 표준 lock
문을 사용하는 경우 잠금 개체를 인수로 제공해야합니다. 사용자 지정 동기화를 사용하는 경우 CollectionSynchronizationCallback
대리자와 컨텍스트 개체 (일 수 있음 null
) 를 제공해야합니다 . 호출 될 때이 대리자는 사용자 지정 잠금을 획득하고 Action
전달 된 잠금을 호출하고 반환하기 전에 잠금을 해제해야합니다.
3. 컬렉션을 수정하기 전에 잠금하여 협력
컬렉션을 직접 수정하려는 경우에도 동일한 메커니즘을 사용하여 컬렉션을 잠 가야합니다. 이 작업을 수행 lock()
에 전달 된 동일한 잠금 개체에 대한 EnableCollectionSynchronization
간단한 시나리오에서, 또는 사용자 정의 시나리오에서 동일한 사용자 정의 동기화 메커니즘.
답변
.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)));
답변
후손을위한 컬렉션 동기화 코드입니다. 이것은 단순한 잠금 메커니즘을 사용하여 컬렉션 동기화를 활성화합니다. 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);
}
}
}