1

私はRxに少し慣れていないので、これがばかげているか明白であると思われる場合は失礼します...

ある時点で、選択したフォルダをスキャンしてすべてのファイルを再帰的に取得し、その後データベースに保存する必要があるアプリケーションがあります。もちろん、UIの応答性を維持しながら、そのプロセス中に進行状況バーを表示したいと思います。キャンセルボタンも後の段階でいいでしょう。

私は次のようにRxを使用してこれを実装しました:

// Get a list of all the files
var enumeratedFiles = Directory.EnumerateFiles(targetDirectory, "*.*", SearchOption.AllDirectories);

// prepare the progress bar
            double value = 0;
            progBar.Minimum = 0;
            progBar.Maximum = enumeratedFiles.Count();
            progBar.Value = value;
            progBar.Height = 15;
            progBar.Width = 100;
            statusBar.Items.Add(progBar);

var files = enumeratedFiles.ToObservable()
                .SubscribeOn(TaskPoolScheduler.Default)
                .ObserveOnDispatcher()
                .Subscribe(x =>
                    {
                        myDataSet.myTable.AddTableRow(System.IO.Path.GetFileNameWithoutExtension(x));
                        value++;
                    },
                    () =>
                    {
                        myDataSetTableAdapter.Update(myDataSet.myTable);
                        myDataSetTableAdapter.Fill(myDataSet.myTable);
                        statusBar.Items.Remove(progBar);
                    });

ただし、上記の例では、UIがロックされており、プロセス中に進行状況バーが更新されません。これは、SubscribeOn(TaskPoolScheduler)が新しいスレッドでタスクを実行することになっていると信じていたのに、AddTableRowメソッドがスレッドをブロックしているためだと思いますか?

私もいくつかの異なるアプローチを試しましたが、結果はさまざまです。たとえば、.Do行を追加します。

var files = enumeratedFiles.ToObservable()
                .Do(x => myDataSet.myTable.AddTableRow(System.IO.Path.GetFileNameWithoutExtension(x)))
                .SubscribeOn(TaskPoolScheduler.Default)
                .ObserveOnDispatcher()
                .Subscribe(x =>
                    {
                        value++;
                    },
                    () =>
                    {
                        myDataSetTableAdapter.Update(myDataSet.myTable);
                        myDataSetTableAdapter.Fill(myDataSet.myTable);
                        statusBar.Items.Remove(progBar);
                        btnCancel.Visibility = Visibility.Collapsed;
                    });

これは実際にはプログレスバーの更新を示しており、UIは完全にはロックされていませんが、途切れ途切れでパフォーマンスが低下しています...

私はBackgroundWorkerを使用して同じジョブを実行しようとしましたが、パフォーマンスは上記のRxアプローチよりもはるかに劣っています(たとえば、21000ファイルの場合、Rxアプローチは数秒かかりますがBackgroundWorkerは数分で終了します)。

プログレスバーのValuePropertyメソッドのDelegatesでも同様の問題に取り組んでいますが、可能であればRxを使用してこれを解決したいと思います。

ここで明らかな何かが欠けていますか?任意の提案をいただければ幸いです...

4

3 に答える 3

1

私は解決策を見つけました、そして何が起こっているのかについてもう少し詳しく:

前述の DataSet に行を挿入する際の遅延は、デバッグ モードでのみ発生しますが、デバッグなしで実行すると、アプリケーションはその遅延を表示せず、プログレス バーとアイテムの数は何倍も速く処理されます。以前にテストしなかったのはばかげています...

ファイルを再帰的にスキャンしている間、わずかな遅延 (21000 ファイルの場合は数秒) がありますが、それは最初にそれを行うときにのみ発生するため、その後のテストでは気付かず、思われる部分にのみ焦点を合わせていました。私には遅い:DataSetの充填。Directory.EnumerateFiles はすべてをメモリにキャッシュするので、同じファイルを読み取ろうとする他の試みは即座に完了すると思いますか?

また、 myDataSetTableAdapter.Fill(myDataSet.myTable) 行は必要ないようです.Updateメソッドはすでにデータベース自体にコンテンツを保存しているためです。

私のために働いた最終的なコードスニペットは次のとおりです。

progBar.Height = 15;
progBar.Width = 100;
progBar.IsIndeterminate = true;
statusBar.Items.Add(progBar);

var files = Directory.EnumerateFiles(targetDirectory, "*.*", SearchOption.AllDirectories)
            .Where(s => extensions.Contains(System.IO.Path.GetExtension(s))) // "extensions" has been specified further above in the code
            .ToObservable(TaskPoolScheduler.Default)
            .Do(x => myDataSet.myTable.AddTableRow(System.IO.Path.GetFileNameWithoutExtension(x), x, "default")) // my table has 3 columns
            .TakeLast(1)
            .Do(_ => myDataSetmyTableTableAdapter.Update(myDataSet.myTable))
            .ObserveOnDispatcher()
            .Subscribe(xy =>
                {
                    //progBar.Value++; //commented out since I've switched to a marquee progress bar
                },
                () =>
                {
                    statusBar.Items.Remove(progBar);
                    btnCancel.Visibility = Visibility.Collapsed;
                });

これは私にとってはうまくいくようです。助けてくれてありがとう!

編集:キャンセルボタン機能を含めるために、上記をさらに拡張しました。ユーザーが [キャンセル] ボタンをクリックすると、プロセスはすぐに停止します。できるだけエレガントに保つように努めたので、[キャンセル] ボタンの Click イベントから Observable を追加し、上記の既存のファイル Observable で .TakeUntil を使用しました。コードは次のようになります。

// Show the Cancel button to allow the user to abort the process
btnCancel.Visibility = Visibility.Visible;

// Set the Cancel click event as an observable so we can monitor it
var cancelClicked = Observable.FromEventPattern<EventArgs>(btnCancel, "Click");

// Use Rx to pick the scanned files from the IEnumerable collection, fill them in the DataSet and finally save the DataSet in the DB
var files = Directory.EnumerateFiles(targetDirectory, "*.*", SearchOption.AllDirectories)
            .Where(s => extensions.Contains(System.IO.Path.GetExtension(s)))
            .ToObservable(TaskPoolScheduler.Default)
            .TakeUntil(cancelClicked)
            .Do(x => ....
于 2012-11-04T11:19:12.093 に答える
1

あなたが書いたもののセマンティクスはかなり奇妙です:

  1. UI スレッドでファイル操作全体を実行する
  2. それらのアイテムを取得してから、新しいスレッドを生成します
  3. そのスレッドで、Dispatcher.BeginInvoke
  4. 呼び出しで、表の行を追加します

これはあなたが本当に望んでいるようには見えません...

于 2012-11-03T01:44:05.400 に答える
0

コードを読むと、バックグラウンドで多くの作業を行い、最後に UI を更新しているように見えます。この種の作業では、enumeratedFiles変数を次のように定義する方がよい場合があります。

var enumeratedFiles =
    Observable
        .Start(() =>
            Directory
                .EnumerateFiles(
                    targetDirectory, "*.*", SearchOption.AllDirectories),
                        Scheduler.TaskPool)
        .ObserveOnDispatcher();

比較的迅速なバックグラウンド操作が行われた後、UI が 1 回更新されます。これは、現在のアプローチのより良い実装です。

返された各ファイルの UI を更新する方法がわかった場合は、代わりに次のオブザーバブルを試してください。

var enumeratedFiles =
    Directory
        .EnumerateFiles(targetDirectory, "*.*", SearchOption.AllDirectories)
        .ToObservable(Scheduler.TaskPool)
        .ObserveOnDispatcher();

このオプションを使用すると、見つかったファイルごとに UI を更新する方法を明らかにする必要があります。

どちらかがうまくいくかどうか教えてください。

于 2012-11-03T10:01:12.510 に答える