programing

작업자 스레드를 통해 관찰 가능한 컬렉션을 업데이트하려면 어떻게 해야 합니까?

cafebook 2023. 5. 18. 23:31
반응형

작업자 스레드를 통해 관찰 가능한 컬렉션을 업데이트하려면 어떻게 해야 합니까?

나는 가지고 있습니다.ObservableCollection<A> a_collection;컬렉션에 'n'개의 항목이 포함되어 있습니다.의 A 다음과 .

public class A : INotifyPropertyChanged
{

    public ObservableCollection<B> b_subcollection;
    Thread m_worker;
}

view + , WPF 목록 뷰 + Details view 컨트롤을 .b_subcollection선택한 항목을 별도의 목록 보기에 표시합니다(2방향 바인딩, 변경된 속성에 대한 업데이트 등).

스레드화를 구현하기 시작했을 때 문제가 나타났습니다.전체적인 아이디어는 전체를 갖는 것이었습니다.a_collection work를 한 의 "do"를 합니다.b_subcollections그리고 GUI가 실시간으로 결과를 보여주도록 합니다.

제가 그것을 시도했을 때, 디스패처 스레드만이 관찰 가능한 컬렉션을 수정할 수 있다는 예외가 있었고 작업이 중단되었습니다.

누가 그 문제와 그 해결 방법을 설명해 줄 수 있습니까?

.NET 4.5의 새로운 옵션

4하고 .NET 4.5를 디스패치하는 .CollectionChanged 이벤트를 UI 스레드로 전송합니다.이 기능을 사용하려면 UI 스레드 내에서 호출해야 합니다.

EnableCollectionSynchronization두 가지 작업을 수행합니다.

  1. 를 기억하고 .CollectionChanged해당 스레드의 이벤트.
  2. 마샬링된 이벤트가 처리될 때까지 컬렉션에 대한 잠금을 획득하여 UI 스레드를 실행하는 이벤트 핸들러가 백그라운드 스레드에서 수정되는 동안 컬렉션을 읽으려고 하지 않도록 합니다.

매우 중요한 것은 이것이 모든 것을 해결하지 않는다는 것입니다. 본질적으로 스레드 세이프가 아닌 컬렉션에 대한 스레드 세이프 액세스를 보장하려면 컬렉션이 수정되려고 할 때 백그라운드 스레드에서 동일한 잠금을 획득하여 프레임워크와 협력해야 합니다.

따라서 올바른 작동에 필요한 단계는 다음과 같습니다.

사용할 잠금 유형 결정

다음 중 어떤 과부하가 발생하는지 결정합니다.EnableCollectionSynchronization사용해야 합니다.한 분의시단순다니합간은부대▁most▁simple다니단순▁a합▁of▁time대.lock명령문으로 충분하므로 이 오버로드가 표준 선택이지만 고급 동기화 메커니즘을 사용하는 경우 사용자 지정 잠금도 지원됩니다.

컬렉션을 만들고 동기화 사용

선택한 잠금 메커니즘에 따라 UI 스레드에서 적절한 오버로드를 호출합니다.표준을 사용하는 경우lock잠금 개체를 인수로 제공해야 하는 문입니다.사용자 지정 동기화를 사용하는 경우 대리자와 컨텍스트 개체를 제공해야 합니다.null이은 호출 시 정의 잠금을 한 후 를 .Action되돌아오기 전에 잠금을 해제합니다.

컬렉션을 수정하기 전에 잠그는 방식으로 협력

또한 컬렉션을 직접 수정하려는 경우 동일한 메커니즘을 사용하여 컬렉션을 잠가야 합니다. 다음 작업을 수행합니다.lock() EnableCollectionSynchronization사용자 지정 시나리오에서 동일한 사용자 지정 동기화 메커니즘을 사용할 수 있습니다.

기술적으로 문제는 백그라운드 스레드에서 관찰 가능한 컬렉션을 업데이트하는 것이 아닙니다.문제는 이렇게 하면 컬렉션이 변경된 동일한 스레드에서 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 스레드에 보고됩니다.이것은 당신의 또 다른 선택사항일 수 있습니다.

.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);
        }
    }
}

동기화 컨텍스트를 사용했습니다.

SynchronizationContext SyncContext { get; set; }

생성자:

SyncContext = SynchronizationContext.Current;

백그라운드 작업자 또는 이벤트 처리기에서:

SyncContext.Post(o =>
{
    ObservableCollection.AddRange(myData);
}, null);

마이크로소프트 문서

UI에 대한 플랫폼 코드(레이아웃, 입력, 이벤트 발생 등)와 UI에 대한 앱의 코드가 모두 동일한 UI 스레드에서 실행됩니다.

ObservableCollection키우고 있습니다CollectionChanged다음 작업 중 하나가 발생하는 이벤트:추가, 제거, 교체, 이동, 재설정...또한 이 이벤트는 UI 스레드에서 발생해야 합니다. 그렇지 않으면 발신자 스레드에서 예외가 발생합니다.

이 유형의 CollectionView는 디스패처 스레드와 다른 스레드에서 원본 컬렉션을 변경할 수 없습니다.

UI가 업데이트되지 않습니다.

백그라운드 스레드에서 UI를 업데이트하려면 응용 프로그램의 디스패처에서 코드를 실행합니다.

Application.Current.Dispatcher.Invoke(() => {
    // update UI
});

@존 답변은 좋지만 코드 샘플이 부족합니다.

// UI thread
var myCollection = new ObservableCollection<string>();
var lockObject = new object();
BindingOperations.EnableCollectionSynchronization(myCollection, lockObject );

[..]

// Non UI thread
lock (lockObject) 
{
   myCollection.Add("Foo")
}

또한 참고:CollectionChanged이벤트 핸들러가 UI가 아닌 스레드에서 계속 호출됩니다.

언급URL : https://stackoverflow.com/questions/2091988/how-do-i-update-an-observablecollection-via-a-worker-thread

반응형