C# ManualResetEvent WaitOne阻止my CollectionView的所有者线程

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

我已经编写了一个WPF WizardFramework,它使用一些
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();