6

いくつかのBackgroundWorker. ObservableCollection処理中に、UI にバインドされている を更新する必要がある場合があります。

この場合、とThreadableObservableCollectionのスレッドセーフなメソッドを提供する を作成しました。私は.NET 4.5を使用していますが、他の多くの無効なアクセス例外なしでは機能しませんでした。私のように見えます:InsertRemoveRemoveAtBindingOperations.EnableCollectionSynchronizationCollection

  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-Collection に挿入されたいくつかのページを探すリスナーがあります。非同期作業が完了したら、Validate を使用してビューモデルの状態を確認できます。

残念ながらManualResetEvent、ウィザードが閉じるのを待つために a を使用しています。これは次のようになります。

  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 です。コレクションを更新したいのですが、 AppDomainThreadsafeInsertスレッドを使用できません。

しかし、ウィザードが終了するまで待たなければなりません。どうすればこの種のデッドロックを解決できますか? または、これに対するよりエレガントなソリューションはありますか?

編集: ユーザーインターフェイスがあるかどうかを確認してこの問題を回避し、それからアプリケーションスレッドで呼び出します。それ以外の場合は、別のスレッドで意図的にコレクションを変更します。これは例外を防ぎませんが、テストからは認識されません...それにもかかわらず、アイテムは挿入されますが、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 全体を作成し、これを使用してコレクションを変更する方法はありますか?

4

2 に答える 2

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

それでも解決しない場合は、SynchronisationContext の回避策を試す必要があると思います。

于 2013-12-25T02:56:26.687 に答える