ManualResetEvent WaitOne阻止CollectionView的所有者线程

本文关键字:所有者 线程 CollectionView WaitOne 阻止 ManualResetEvent | 更新日期: 2024-10-24 03:15:18

我已经编写了一个WPF向导框架,它使用一些BackgroundWorker在后台执行一些操作。在处理过程中,我可能不得不更新绑定到我的UI的ObservableCollection

对于这种情况,我编写了一个ThreadableObservableCollection,它为InsertRemoveRemoveAt提供了线程安全方法。虽然我在用。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,并使用它来更改我的集合?

ManualResetEvent WaitOne阻止CollectionView的所有者线程

正如我所看到的主要问题,主线程被阻塞,其他操作也试图在主线程中执行?不要阻塞主线程,比如这样:

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