ManualResetEvent WaitOne阻止CollectionView的所有者线程
本文关键字:所有者 线程 CollectionView WaitOne 阻止 ManualResetEvent | 更新日期: 2024-10-24 03:15:18
我已经编写了一个WPF向导框架,它使用一些BackgroundWorker
在后台执行一些操作。在处理过程中,我可能不得不更新绑定到我的UI的ObservableCollection
。
对于这种情况,我编写了一个ThreadableObservableCollection
,它为Insert
、Remove
和RemoveAt
提供了线程安全方法。虽然我在用。NET 4.5如果没有许多其他无效访问异常,我无法使BindingOperations.EnableCollectionSynchronization
正常工作。我的Collection
看起来像:
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();
});
}
}
[..]
}
当我在应用程序中使用向导时,这将按预期工作。现在我正在使用NUnit为应用程序编写一些集成测试。
有一个侦听器,它等待WizardViewModel完成它的工作,并查找在Steps集合中注入的一些页面。异步工作完成后,我可以使用Validate来检查视图模型的状态。
不幸的是,我正在使用ManualResetEvent
来等待向导关闭。如下所示:
public class WizardValidator : IValidator, IDisposable
{
private WizardViewModel _dialog;
private readonly ManualResetEvent _dialogClosed = new ManualResetEvent(false);
[..]
public void ListenTo(WizardViewModel dialog)
{
_dialog = dialog;
dialog.RequestClose += (sender, args) => _dialogClosed.Set();
dialog.StepsDefaultView.CurrentChanged += StepsDefaultViewOnCurrentChanged;
_dialogClosed.WaitOne();
}
[..]
}
现在出现了一个问题:当应用程序正在运行时,UI线程没有被阻止,可以毫无问题地更新集合。但在我的测试用例中,我初始化ViewModel(以及集合)的"主"线程是一个被测试代码阻止的AppDomainThread。现在我的ThreadsafeInsert
想要更新集合,但不能使用AppDomain线程。
但我必须等待巫师完成,我该如何解决这种僵局?或者有更优雅的解决方案吗?
编辑:我通过检查是否有用户界面来解决这个问题,然后我才在应用程序线程上调用,否则我会有意在另一个线程上更改集合。这并不能阻止异常,但它没有从测试中识别出来。。。然而,项目被插入,只有NotifyCollectionChanged
-Handler没有被调用(无论如何,它只在UI中使用)。
if (Application.Current != null)
{
Application.Current.Dispatcher.Invoke(() =>
{
Steps.Insert(pos, step);
stepsView.MoveCurrentTo(step);
});
}
else
{
new Action(() => Steps.Insert(pos, step)).BeginInvoke(ar => stepsView.MoveCurrentToPosition(pos), null);
}
这是一个丑陋的解决方案,我仍然对一个干净的解决方案感兴趣。
有没有办法使用备用Dispatcher来创建(例如)整个ViewModel,并使用它来更改我的集合?
正如我所看到的主要问题,主线程被阻塞,其他操作也试图在主线程中执行?不要阻塞主线程,比如这样:
// 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();
如果它没有帮助,那么我想需要尝试一些同步上下文解决方案。
我认为问题归结为创建绑定到Dispatcher对象的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);
}
}