스레드로부터 안전한 CollectionView는 어디에서 얻을 수 있습니까?
백그라운드 스레드에서 비즈니스 개체 컬렉션을 업데이트 할 때 다음 오류 메시지가 표시됩니다.
이 유형의 CollectionView는 Dispatcher 스레드와 다른 스레드에서 SourceCollection에 대한 변경을 지원하지 않습니다.
알겠습니다. 그러나 또한 여러 스레드를 지원하는 CollectionView 버전이 무엇이며 내 개체가이를 사용하도록 만드는 방법에 대한 질문도 제기합니다.
다음은 Jonathan이 찾은 구현에 대한 개선 사항입니다. 첫째, 모든 이벤트 핸들러가 동일한 (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를 사용하고 있기 때문에 알림을받는 변경 사항이 처리기가 호출되기 전에 취소 될 수 있습니다. 이로 인해 일반적으로 "색인이 범위를 벗어났습니다."가 발생합니다. 이벤트 인수가 목록의 새 (변경된) 상태에 대해 검사 될 때 예외가 발생합니다. 이를 방지하기 위해 모든 지연된 이벤트는 재설정 이벤트로 대체됩니다. 이로 인해 경우에 따라 과도한 다시 그리기가 발생할 수 있습니다.
사용하다:
System.Windows.Application.Current.Dispatcher.Invoke(
System.Windows.Threading.DispatcherPriority.Normal,
(Action)delegate()
{
// Your Action Code
});
Bea Stollnitz 의이 게시물 은 오류 메시지와 그것이 그대로 표현 된 이유를 설명 합니다 .
편집 : Bea의 블로그에서
불행히도이 코드는 "NotSupportedException –이 유형의 CollectionView는 Dispatcher 스레드와 다른 스레드에서 SourceCollection에 대한 변경을 지원하지 않습니다."라는 예외가 발생합니다. 이 오류 메시지는 사람들이 사용중인 CollectionView가 스레드 간 변경을 지원하지 않는 경우 해당 변경 사항을 찾아야한다고 생각하게한다는 것을 이해합니다. 글쎄,이 오류 메시지는 약간 오해의 소지가 있습니다. 즉석에서 제공하는 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
"Reset"작업으로 BeginInvoke를 호출 할 때도 문제가 있습니다. "Reset"은 핸들러가 컬렉션 자체를 확인해야하는 유일한 작업입니다. "Reset"을 BeginInvoke 한 다음 즉시 BeginInvoke를 실행하면 처리기가 이미 업데이트 된 컬렉션이있는 "Reset"을 수락하고 다음 "Add"가 엉망이 될 것입니다.
작동하는 내 구현은 다음과 같습니다. 사실 저는 BeginInvoke를 전혀 제거하려고 생각하고 있습니다.
다음과 같이 컬렉션 동기화를 활성화하여 wpf를 사용하여 컬렉션에 대한 교차 스레드 변경 사항을 관리 할 수 있습니다.
BindingOperations.EnableCollectionSynchronization(collection, syncLock);
listBox.ItemsSource = collection;
이는 컬렉션이 UI 스레드에서 수정 될 수 있음을 WPF에 알려주므로 UI 변경 사항을 적절한 스레드로 다시 마샬링해야 함을 알 수 있습니다.
잠금 개체가없는 경우 동기화 콜백을 제공하는 오버로드도 있습니다.
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>
씨#
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
}));
그들 중 누구도 Dispatcher.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
'Program Club' 카테고리의 다른 글
| MySQL에서 특수 문자를 어떻게 이스케이프합니까? (0) | 2020.11.06 |
|---|---|
| 파이썬에서 내장 함수 property ()를 사용하는시기와 방법 (0) | 2020.11.06 |
| 텍스트 파일의 첫 줄과 마지막 줄을 얻는 가장 효율적인 방법은 무엇입니까? (0) | 2020.11.06 |
| 정수의 동적 배열을 만드는 방법 (0) | 2020.11.06 |
| Bash를 사용하여 한 디렉토리를 다른 디렉토리로 어떻게 병합합니까? (0) | 2020.11.06 |