0

ObservableCollection文字列とintを保持するカスタムクラスがあります:

public class SearchFile
{
    public string path { set; get; }
    public int occurrences { set; get; }
}

コレクションを で表示したいdataGrid。コレクションには、更新されるたびに通知するメソッドがあります。これまでのところ、DataGrid.ItemsSource(正しい?) にリンクするだけです。グリッド XAML はdataGrid1.ItemsSource = files;次のとおりです (C# コードビハインドを使用)。

        <DataGrid AutoGenerateColumns="False" Height="260" Name="dataGrid1" VerticalAlignment="Stretch" IsReadOnly="True" ItemsSource="{Binding}" >
            <DataGrid.Columns>
                <DataGridTextColumn Header="path" Binding="{Binding path}" />
                <DataGridTextColumn Header="#" Binding="{Binding occurrences}" />
            </DataGrid.Columns>
        </DataGrid>

現在、事態はより複雑になっています。path最初に、デフォルト値のoccurrenceゼロで sを表示したいと思います。SearchFile次に、すべてを調べて、計算された値で更新したいと思いますoccurrence。ヘルパー関数は次のとおりです。

    public static void AddOccurrences(this ObservableCollection<SearchFile> collection, string path, int occurrences)
    {
        for(int i = 0; i < collection.Count; i++)
        {
            if(collection[i].path == path)
            {
                collection[i].occurrences = occurrences;
                break;
            }
        }
    }

プレースホルダー ワーカー関数は次のとおりです。

    public static bool searchFile(string path, out int occurences)
    {
        Thread.Sleep(1000);
        occurences = 1;
        return true; //for other things; ignore here     
    }

BackgroundWorkerバックグラウンド スレッドとしてa を使用しています。方法は次のとおりです。

    private void worker_DoWork(object sender, DoWorkEventArgs e)
    {           
        List<string> allFiles = new List<string>();
        //allFiles = some basic directory searching

        this.Dispatcher.Invoke(new Action(delegate
        {
            searchProgressBar.Maximum = allFiles.Count;
            files.Clear(); // remove the previous file list to build new one from scratch
        }));

        /* Create a new list of files with the default occurrences value. */
        foreach(var file in allFiles)
        {
            SearchFile sf = new SearchFile() { path=file, occurrences=0 };
            this.Dispatcher.Invoke(new Action(delegate
            {
                files.Add(sf);
            }));
        }

        /* Add the occurrences. */
        foreach(var file in allFiles)
        {
            ++progress; // advance the progress bar
            this.Dispatcher.Invoke(new Action(delegate
            {
                searchProgressBar.Value = progress;
            }));

            int occurences;
            bool result = FileSearcher.searchFile(file, out occurences);

            files.AddOccurrences(file, occurences);
        }
    }

実行すると、2つの問題があります。まず、進行状況バーの値を更新すると、The calling thread cannot access this object because a different thread owns it.例外がスローされます。なんで?ディスパッチャにあるので、問題なく動作するはずです。そして 2 番目に、foreachループがbool result =...行で中断されます。私はそれをコメントアウトして設定しようとしましたint occurences = 1が、ループが回っていますが、何か奇妙なことが起こっています.ゼロ)。

なぜですか?

4

1 に答える 1

0

ディスパッチャは、思ったほど単純ではありません。ディスパッチャを使用するということは、コードが含まれているオブジェクトを作成したのと同じスレッドで実行されることを意味しますが、それは必ずしもUIスレッドではありません。worker_DoWorkメソッドが、画面に表示される実際のUI要素で定義されていることを確認してください(これは、バックグラウンドスレッドによって作成された要素を削除する簡単な方法です)。

実際、コードを見ると、なぜバックグラウンドワーカーを使用しているのかわかりません。これは、UIスレッドへの継続的なディスパッチのため、実際には遅くなるように見えます。代わりに、実行時間の長い部分をタスクに入れて、単一のコールバックでUIを更新する方がよいと思います。たとえば、コードでは、UIスレッドに対して実際に遅すぎるのはFileSearcher呼び出しだけであるように見えます。これは、見つかった結果の数を返すバックグラウンドタスクを配置するのに十分簡単です。

FileSearcherの問題に関しては、メソッド定義が一致していません。投稿したメソッドはパスとoutintだけを取りますが、それを呼び出すと4つのパラメーターを渡します。あなたが実際に呼んでいる過負荷を見ずに、何が悪いのかを推測するのは難しいです。

編集:もう少しバックアップさせてください。根本的な問題は、WPFがバックグラウンドスレッドから変更されたコレクションへのバインドをサポートしていないことです。それが、そこにあるすべてのディスパッチングやその他の複雑なコードの理由です。私の最初の提案(長い作業のための単一のタスク)は、問題を回避する1つの方法でした。しかし、おそらくObservableListクラスを使用するとさらにうまくいく可能性があります。これにより、バックグラウンドスレッドからコレクションを更新し、ディスパッチャーを使用せずにUIに自動的に通知できます。これにより、複雑なスレッドの問題のほとんどが解消される可能性があります。参考までに最初の3つの記事を読むことをお勧めします。そこにはたくさんの良い情報があります。

于 2012-11-21T13:56:20.667 に答える