ManualResetEvent WaitOne阻止我的CollectionView的所有者Thread

Her*_*erm 6 c# wpf nunit dispatcher manualresetevent

我编写了一个WPF WizardFramework,它使用一些在后台执行一些操作BackgroundWorker.在处理时,我必须更新ObservableCollection绑定到我的UI的内容.

对于这种情况,我写了一个ThreadableObservableCollection,它提供线程安全的方法Insert,RemoveRemoveAt.虽然我使用的是.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();
          });
      }
    }

    [..]
  }
Run Code Online (Sandbox Code Playgroud)

这是按预期工作,而我在我的应用程序中使用向导.现在我正在使用NUnit为应用程序编写一些集成测试.

有一个监听器等待WizardViewModel完成它的工作并寻找一些在Steps-Collection中注入的页面.完成asyncrone工作后,我可以使用Validate来检查viewmodel状态.

不幸的是我正在使用a 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();
    }

    [..]
 }
Run Code Online (Sandbox Code Playgroud)

现在出现了一个问题:当应用程序运行时,UI线程未被阻止,可以毫无问题地更新Collection.但是在我的测试用例中,我初始化ViewModel的"主"线程(以及因为它的集合)是一个被测试代码阻止的AppDomainThread.现在我ThreadsafeInsert想更新集合但不能使用AppDomain线程.

但是我必须等待向导完成,我该如何解决这种僵局?或者是否有更优雅的解决方案?

编辑: 我解决了这个问题,检查是否有用户界面,然后我才在Application-Thread上调用,否则我会故意在另一个线程上更改集合.这不会阻止异常,但是从测试中无法识别...但是仍然插入了项目,只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);  
  }
Run Code Online (Sandbox Code Playgroud)

这是一个丑陋的解决方法,我仍然对一个干净的解决方案感兴趣.

有没有办法使用备用Dispatcher来创建(例如)整个ViewModel并使用它来更改我的集合?

Ton*_*ony 3

正如我看到的主要问题是主线程被阻塞并且其他操作也试图在主线程中执行?不阻塞主线程怎么样,如下所示:

// 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();
Run Code Online (Sandbox Code Playgroud)

如果它没有帮助,那么我想需要尝试一些 SynchronizationContext 解决方法。