简体   繁体   English

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

[英]ManualResetEvent WaitOne blocks the owner Thread of my CollectionView

I've written a WPF WizardFramework which performs some actions in the background using some BackgroundWorker . 我编写了一个WPF WizardFramework,它使用一些BackgroundWorker在后台执行一些操作。 While processing it can happen that I have to update an ObservableCollection which is bound to my UI. 在处理时,我必须更新绑定到我的UI的ObservableCollection

For this case I've written a ThreadableObservableCollection , which provides threadsafe methods for Insert , Remove and RemoveAt . 对于这种情况,我编写了一个ThreadableObservableCollection ,它为InsertRemoveRemoveAt提供了线程安全的方法。 Though I'm using .NET 4.5 I was not able to get BindingOperations.EnableCollectionSynchronization working without many other invalid access exceptions. 虽然我使用的是.NET 4.5,但是我无法在没有许多其他无效访问异常的情况下使BindingOperations.EnableCollectionSynchronization工作。 My Collection looks like: 我的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();
          });
      }
    }

    [..]
  }

This is working as expected, while I am using the wizard in my application. 这是按预期工作,而我在我的应用程序中使用向导。 Now I'm using NUnit to write some integrationtests for the application. 现在我正在使用NUnit为应用程序编写一些集成测试。

There's a listener which waits for the WizardViewModel to finish it's work and looking for some pages which are injected in the Steps-Collection. 有一个监听器等待WizardViewModel完成它的工作并寻找一些在Steps-Collection中注入的页面。 After the asyncrone work is done I can use Validate to check the viewmodel state. 完成asyncrone工作后,我可以使用Validate来检查viewmodel状态。

Unfortunately I'm using a ManualResetEvent to wait for the wizard to close. 不幸的是我正在使用ManualResetEvent来等待向导关闭。 This looks like following: 这看起来如下:

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

    [..]
 }

Now there's a problem: While the Application is running the UI Thread is not blocked, the Collection can be updated without any problems. 现在出现了一个问题:当应用程序运行时,UI线程未被阻止,可以毫无问题地更新Collection。 But in my testcases the "main" Thread where I initialize the ViewModel (and because of that the Collections) is an AppDomainThread which is blocked by the testcode. 但是在我的测试用例中,我初始化ViewModel的“主”线程(以及因为它的集合)是一个被测试代码阻止的AppDomainThread。 Now my ThreadsafeInsert wants to update the collection but cannot use the AppDomain Thread. 现在我的ThreadsafeInsert想要更新集合但不能使用AppDomain线程。

But I have to wait for the wizard to finish, how can I solve this kind of deadlock? 但是我必须等待向导完成,我该如何解决这种僵局? Or is there a more elegant solution for this one? 或者是否有更优雅的解决方案?

edit: I worked around this problem with a check if there's a user interface, and only then I invoke on the Application-Thread, otherwise I change the collection intentionally on another thread. 编辑:我解决了这个问题,检查是否有用户界面,然后我才在Application-Thread上调用,否则我会故意在另一个线程上更改集合。 This does not prevent the exception, but it is not recognized from the test... the items are inserted nevertheless, only the NotifyCollectionChanged -Handler is not called (which is only used in the UI anyway). 这不会阻止异常,但是从测试中无法识别...但是插入了项目,但是没有调用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);  
  }

This is an ugly workaround and I am still interested in a clean solution. 这是一个丑陋的解决方法,我仍然对一个干净的解决方案感兴趣。

Is there a way to use an alternate Dispatcher to create (eg) the whole ViewModel and use this to change my collection? 有没有办法使用备用Dispatcher来创建(例如)整个ViewModel并使用它来更改我的集合?

As I see the main problem that main thread is blocked and other operations are trying to be executed in main thread too? 我看到主线程被阻塞的主要问题,其他操作也试图在主线程中执行? What about not to block main thread, like this: 如何不阻止主线程,如下所示:

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

If it will not help then I guess need to try some SynchronisationContext workarounds. 如果它没有帮助那么我想需要尝试一些SynchronisationContext解决方法。

I think the problems boil down to the fact that you create ObservableCollection that is tied to Dispatcher object. 我认为问题归结为你创建了与Dispatcher对象绑定的ObservableCollection。

Involving Dispatcher object directly is almost never good idea(as you just witnessed). 直接涉及Dispatcher对象几乎从来都不是一个好主意(正如您刚才所见)。 Instead I would suggest you to see how others have implemented ThreadSafeObservableCollection. 相反,我建议你看看其他人是如何实现ThreadSafeObservableCollection的。 This is a little example I put together, it should illustrate the point: 这是我放在一起的一个小例子,它应该说明一点:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM