C# ManualResetEvent WaitOne阻止my CollectionView的所有者线程
我已经编写了一个WPF WizardFramework,它使用一些C# ManualResetEvent WaitOne阻止my CollectionView的所有者线程,c#,wpf,nunit,dispatcher,manualresetevent,C#,Wpf,Nunit,Dispatcher,Manualresetevent,我已经编写了一个WPF WizardFramework,它使用一些BackgroundWorker在后台执行一些操作。在处理过程中,可能需要更新绑定到UI的ObservableCollection 对于本例,我编写了一个threadableobservecollection,它为Insert、Remove和RemoveAt提供线程安全方法。虽然我使用的是.NET 4.5,但在没有许多其他无效访问异常的情况下,BindingOperations.EnableCollectionSynchroniz
BackgroundWorker
在后台执行一些操作。在处理过程中,可能需要更新绑定到UI的ObservableCollection
对于本例,我编写了一个threadableobservecollection
,它为Insert
、Remove
和RemoveAt
提供线程安全方法。虽然我使用的是.NET 4.5,但在没有许多其他无效访问异常的情况下,BindingOperations.EnableCollectionSynchronization
无法正常工作。我的收藏
看起来像:
public class ThreadableObservableCollection<T> : ObservableCollection<T>
{
private readonly Dispatcher _dispatcher;
public ThreadableObservableCollection()
{
_dispatcher = Dispatcher.CurrentDispatcher;
}
public void ThreadsafeInsert(int pos, T item, Action callback)
{
if (_dispatcher.CheckAccess())
{
Insert(pos, item);
callback();
}
else
{
_dispatcher.Invoke(() =>
{
Insert(pos, item);
callback();
});
}
}
[..]
}
现在有一个问题:
当应用程序运行时,UI线程不会被阻止,集合可以在没有任何问题的情况下更新。但是在我的测试用例中,初始化ViewModel(以及集合)的“主”线程是一个AppDomainThread,它被测试代码阻塞。现在我的ThreadsafeInsert
想要更新集合,但无法使用AppDomain线程
但我必须等待向导完成,如何解决这种死锁?还是有更优雅的解决方案
编辑:
我通过检查是否有用户界面来解决这个问题,然后才在应用程序线程上调用,否则我会在另一个线程上故意更改集合。这并不能阻止异常,但无法从测试中识别它。。。虽然插入了这些项,但仅未调用NotifyCollectionChanged
-处理程序(该处理程序仅在UI中使用)
这是一个丑陋的解决方法,我仍然对一个干净的解决方案感兴趣
有没有一种方法可以使用备用调度程序来创建(例如)整个ViewModel并使用它来更改我的集合?我认为问题可以归结为这样一个事实:您创建的ObservableCollection与调度程序对象相关联 直接涉及Dispatcher对象几乎从来都不是一个好主意(正如您刚才看到的)。相反,我建议您看看其他人是如何实现ThreadSafeObservableCollection的。这是我放在一起的一个小例子,它应该说明这一点:
public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
private readonly object _lock = new object();
public ThreadSafeObservableCollection()
{
BindingOperations.CollectionRegistering += CollectionRegistering;
}
protected override void InsertItem(int index, T item)
{
lock (_lock)
{
base.InsertItem(index, item);
}
}
private void CollectionRegistering(object sender, CollectionRegisteringEventArgs e)
{
if (e.Collection == this)
BindingOperations.EnableCollectionSynchronization(this, _lock);
}
}
公共类ThreadSafeObservableCollection:ObservableCollection
{
私有只读对象_lock=新对象();
公共线程安全ObservableCollection()
{
BindingOperations.CollectionRegistrating+=CollectionRegistrating;
}
受保护的覆盖无效插入项(int索引,T项)
{
锁
{
基本插入项(索引,项目);
}
}
私有void CollectionRegistrating(对象发送方、CollectionRegisteringEventArgs e)
{
if(e.Collection==this)
BindingOperations.EnableCollectionSynchronization(此锁);
}
}
我看到的主要问题是主线程被阻塞,其他操作也试图在主线程中执行?不阻塞主线程怎么样,如下所示:
// helper functions
public void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
public object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
// in your code:
while(!_dialogClosed.WaitOne(200))
DoEvents();
如果没有帮助,我想需要尝试一些SynchronizationContext解决方法。这是一个常见的问题。标准答案在中。好吧,我不应该用ManualResetEvent等待这个,而是按下DispatcherFrame来等待对话框的结果?似乎合理。请使用application.current.dispatcher而不是dispatcher.currentdispatcher。看看这个链接:Windows创建了模态对话框来解决这个问题,为什么不使用模态对话框?@devhedgehog Application.Current在单元测试时为空。使用DispatcherFrame是HansPassant在问题的评论中写的。这是解决阻塞调度程序问题的唯一(干净)方法,也是在unittests中等待asyncrone对话框的正确方法
public class ThreadSafeObservableCollection<T> : ObservableCollection<T>
{
private readonly object _lock = new object();
public ThreadSafeObservableCollection()
{
BindingOperations.CollectionRegistering += CollectionRegistering;
}
protected override void InsertItem(int index, T item)
{
lock (_lock)
{
base.InsertItem(index, item);
}
}
private void CollectionRegistering(object sender, CollectionRegisteringEventArgs e)
{
if (e.Collection == this)
BindingOperations.EnableCollectionSynchronization(this, _lock);
}
}
// helper functions
public void DoEvents()
{
DispatcherFrame frame = new DispatcherFrame();
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.Background,
new DispatcherOperationCallback(ExitFrame), frame);
Dispatcher.PushFrame(frame);
}
public object ExitFrame(object f)
{
((DispatcherFrame)f).Continue = false;
return null;
}
// in your code:
while(!_dialogClosed.WaitOne(200))
DoEvents();