ManualResetEvent WaitOne阻止了我的CollectionView的所有者线程 [英] ManualResetEvent WaitOne blocks the owner Thread of my CollectionView

查看:65
本文介绍了ManualResetEvent WaitOne阻止了我的CollectionView的所有者线程的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我编写了一个WPF WizardFramework,它使用一些 BackgroundWorker 在后台执行一些动作.在处理过程中,可能必须更新绑定到我的UI的 ObservableCollection .

I've written a WPF WizardFramework which performs some actions in the background using some BackgroundWorker. While processing it can happen that I have to update an ObservableCollection which is bound to my UI.

在这种情况下,我编写了 ThreadableObservableCollection ,它为 Insert Remove RemoveAt .尽管我使用的是.NET 4.5,但没有许多其他无效的访问异常,我仍然无法运行 BindingOperations.EnableCollectionSynchronization .我的 Collection 如下:

For this case I've written a ThreadableObservableCollection, which provides threadsafe methods for Insert, Remove and RemoveAt. Though I'm using .NET 4.5 I was not able to get BindingOperations.EnableCollectionSynchronization working without many other invalid access exceptions. My Collection looks like:

  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为应用程序编写一些集成测试.

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.

有一个侦听器,它等待WizardViewModel完成工作,并寻找一些注入到Steps-Collection中的页面.异步工作完成后,我可以使用Validate检查视图模型状态.

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. After the asyncrone work is done I can use Validate to check the viewmodel state.

不幸的是,我正在使用 ManualResetEvent 来等待向导关闭.如下所示:

Unfortunately I'm using a ManualResetEvent to wait for the wizard to close. 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();
    }

    [..]
 }

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

Now there's a problem: While the Application is running the UI Thread is not blocked, the Collection can be updated without any problems. 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. Now my ThreadsafeInsert wants to update the collection but cannot use the AppDomain Thread.

但是我必须等待向导完成,如何解决这种僵局?还是对此有一个更优雅的解决方案?

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?

修改:我通过检查是否存在用户界面来解决此问题,然后才在Application-Thread上调用,否则我有意在另一个线程上更改集合.这不会阻止该异常,但是无法从测试中识别出该异常...尽管如此,仍然插入了这些项,仅未调用 NotifyCollectionChanged -Handler(无论如何仅在UI中使用).

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. 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).

  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.

是否可以使用替代的Dispatcher创建(例如)整个ViewModel并使用它来更改我的收藏集?

Is there a way to use an alternate Dispatcher to create (e.g.) the whole ViewModel and use this to change my collection?

推荐答案

我看到的主要问题是主线程被阻塞,其他操作也试图在主线程中执行?怎么不这样阻塞主线程呢?

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

如果这没有帮助,那么我想需要尝试一些SynchronisationContext解决方法.

If it will not help then I guess need to try some SynchronisationContext workarounds.

这篇关于ManualResetEvent WaitOne阻止了我的CollectionView的所有者线程的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆