【发布时间】:2014-01-05 05:01:39
【问题描述】:
我编写了一个 WPF WizardFramework,它使用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-Collection 中的一些页面。异步工作完成后,我可以使用 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);
}
这是一个丑陋的解决方法,我仍然对干净的解决方案感兴趣。
有没有办法使用备用调度程序来创建(例如)整个 ViewModel 并使用它来更改我的集合?
【问题讨论】:
-
这是一个常见问题。规范答案在this SO question。
-
好的,所以我不应该用 ManualResetEvent 等待这个,而是推送一个 DispatcherFrame 来等待对话框的结果?看起来很合理。
-
使用 application.current.dispatcher 而不是 dispatcher.currentdispatcher。看看这个链接:stackoverflow.com/questions/10448987/…
-
Windows 创建了模态对话框来解决这个问题,你为什么不使用模态对话框?
-
@devhedgehog Application.Current 在单元测试时为空。
标签: c# wpf nunit dispatcher manualresetevent