いくつかのBackgroundWorker
. ObservableCollection
処理中に、UI にバインドされている を更新する必要がある場合があります。
この場合、とThreadableObservableCollection
のスレッドセーフなメソッドを提供する を作成しました。私は.NET 4.5を使用していますが、他の多くの無効なアクセス例外なしでは機能しませんでした。私のように見えます:Insert
Remove
RemoveAt
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();
});
}
}
[..]
}
アプリケーションでウィザードを使用している間、これは期待どおりに機能しています。現在、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 全体を作成し、これを使用してコレクションを変更する方法はありますか?