3

質問を簡単にするために、スレッドを使用して文字列の置換をオンザフライで更新するとします。私の実際のコードでは、スレッドが必要です。私はこの簡単な例でそれを避けることができることを知っています。

したがって、私のソフトウェアには2つのフィールドがあります。ユーザーはファイルを選択し、(一種の)正規表現を記述して、文を入力すると同時に変更の結果を確認します。ユーザーがファイルを選択したときにスレッドを開始します(listViewFiles_SelectionChangedメソッドを参照)。私のスレッドの仕事はDoWorkメソッドにあります。

public void DoWork()
    {
        while (true)
        {
            FileData fileData = _selectedFile;
            if (fileData != null)
            {
                string name = fileData.FileName;
                string searchRegEx = GenerateRegex(_searchTextBox.Text);
                string replacement = _replaceTextBox.Text;
                name = Regex.Replace(name, searchRegEx, replacement);
                /*
                foreach (var action in _actionCollection)
                {
                    name = action.Rename(name);
                }*/

                _searchSample.Content = fileData.FileName;
                _replaceSample.Content = name;
            }
            Thread.Sleep(1000);
        }
    }

    private void listViewFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _selectedFile = listViewFiles.SelectedItem as FileData;
        _thread.Start();
    }

私のスレッドが彼の仕事をするとき、私は行文字列で例外を受け取りますsearchRegEx = GenerateRegex(_searchTextBox.Text); :別のスレッドがオブジェクトを所有しているため、呼び出し元のスレッドはこのオブジェクトにアクセスできません。私はこの例外についてたくさん読みましたが、私はそれを理解していません。

これを修正するために、コードをディスパッチャーで囲みます。メカニズムはわかりませんが、機能します。それが正しいかパフォーマンスかはわかりませんが、機能します。

 public void DoWork()
    {
        while (true)
        {
            FileData fileData = _selectedFile;
            if (fileData != null)
            {
                //use Window.Dispatcher
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                    new Action(delegate()
                    {
                        string name = fileData.FileName;
                        string searchRegEx = GenerateRegex(_searchTextBox.Text);
                        string replacement = _replaceTextBox.Text;
                        name = Regex.Replace(name, searchRegEx, replacement);
                        /*
                        foreach (var action in _actionCollection)
                        {
                            name = action.Rename(name);
                        }*/

                        _searchSample.Content = fileData.FileName;
                        _replaceSample.Content = name;
                    }));
            }
            Thread.Sleep(1000);
        }
    }

    private void listViewFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _selectedFile = listViewFiles.SelectedItem as FileData;
        _thread.Start();
    }

このコードが正しく、適切であるかどうかを知りたいのですが。コメントにforeach命令が表示されます。私のコードは多くの作業を行う必要がありますが、これを遅滞なく行うことが最善の方法であるかどうかはわかりません。ディスパッチャの有用性と優れた実践?

4

4 に答える 4

5

あなたの単一の投稿には多くの質問があると思います、そして私はそれらすべてに対処しようとします:

他のスレッドからのビジュアルコントロールへのアクセス

WinformsとWPFはどちらも、単一のスレッドのみがオブジェクトの状態を変更できるという事実に基づいて構築されており、そのスレッドはもちろん、オブジェクトを作成したスレッドと同じです。

これが重要である理由を想像することができます。コントロールは、自分自身を「レンダリング」または「描画」する方法を知っているオブジェクトです。描画、サイズ変更、移動/ドラッグ中に、オブジェクトのプロパティをコントロール自体の「外部」から変更することはできません。もちろん、メインスレッドはすでに私が述べた変換を行うのに忙しいので、メインスレッドで実行されている「ユーザー」コードがそれを変更しないことが保証されています。ただし、並行して実行されている別のスレッドがまさにそれを実行する可能性があります。たとえば、メインスレッドがaからテキストをレンダリングしていてTextBox、2番目のスレッドがテキストを変更したときに単語の半分が書き込まれたとします。これにより、たとえばテキストの幅の計算で問題が発生します。

ディスパッチャの使用

スレッドディスパッチャが行うことは、コードをメインスレッドに*マーシャリング*することです。ご覧のとおり、WinFormsとWPFを含むほとんどのビジュアルフレームワークは、「アプリケーションループ」に基づいています。これは、アプリケーションがwhile(true){}ブロック内で実行されていることを意味します。コード(たとえばlistViewFiles_SelectionChanged)は、必要に応じてこのループから呼び出されます。この呼び出しを管理するオブジェクトはDispatcherです。実行するもののキューがあり、次に実行することを決定します。ディスパッチャが呼び出したコードが実行されると、アプリケーションの視覚的な部分では他に何も起こりません。結局のところ、それがスレッドが行っていることですよね?そのため、ユーザー入力を処理したり、画面を再レンダリングしたりすることはできません。

は、ディスパッチャによって呼び出される新しいメソッドをキューに挿入することにより、それをポストDispatcherする別のスレッドから呼び出すことができるインターフェイスを提供します。理解できるように、すぐには実行されません。メインを呼び出す2番目のスレッドにいて、メインは画面のレンダリング、入力の処理、またはなどのコードの実行でビジー状態になっている可能性があります。そのループの反復が終了すると、キューをチェックします。メソッドの優先度に応じて、次に実行されるメソッドになる場合もあれば、さらに数回の反復を待つ場合もあります。listViewFiles_SelectionChangedDispatcher

このため、2番目のスレッドで行っているすべてのことをディスパッチャーメソッドに固定すると、フレームワークに2番目のスレッドコードをメインスレッドで実行するように効果的に要求することになります。また、スレッドコードは永久に実行されるため、メインスレッドはそのコード()の実行で永遠に忙しくDoWorkなり、他のことはできなくなります。

ディスパッチャのグッドプラクティス

したがって、上記の項目の結論として、ディスパッチャにコードをマーシャリングすると、メインスレッドはそれを実行するのに忙しくなります。また、ビジー状態の間、アプリケーションは応答しなくなります。常にレスポンシブなアプリが必要なため、メインスレッド、つまりディスパッチャにマーシャリングを依頼するものはすべて、必要なだけ実行する必要があります。

その場合、あなたがしなければならないのは、コントロールにアクセスするマーシャルラインだけです。それがディスパッチャへの多くの呼び出しを意味する場合でも、もちろん、その呼び出しの料金を支払うことになりますが、メインスレッドを必要以上に長くコードに固定するよりはましです。

シングルスレッドでのディスパッチャの使用

Dispatcher、2番目のスレッドがない場合でも役立ちます。長い計算を実行する必要がある場合は、フラグのセットまたはを使用しenumて状態を追跡できます。このメソッドでは、ディスパッチャを呼び出して独自のメソッド(優先度は低い)を渡し、それらを返します(したがって、計算を部分的に分割します)。

于 2012-05-18T15:24:32.803 に答える
1

問題は、コードが_searchTextBox.Textと__replaceTextBox.Textにアクセスしていることです。ディスパッチャを使用したソリューションは機能していますが、実際には何も解決していません。コードはUIスレッドで実行されますが、ListViewの選択が変更された直後には実行されません。

それを機能させるには、ディスパッチャなしで最初のバージョンに戻りますが、スレッド開始引数としてSearchTextとReplaceTextを渡します。ここにいくつかの擬似コードがあります:

var searchText = _searchTextBox.Text;
var replaceText = _replaceTextBox.Text
Thread.Start(() => DoWork(searchText, replaceText));
于 2012-05-18T14:52:57.900 に答える
0

コントロールにアクセスできるのは、が作成されたスレッドのみです。ほとんどの場合、それはUIスレッドです。これはWindowsフォームの場合であり、WPFの場合です。Dispatchを呼び出すと、スレッドがブロックされ、残りはUIスレッドで実行されます。これにより、作業を他のスレッドにオフロードする利点のほとんどが失われ、もちろんUIスレッドが他の作業を実行できなくなります。これに対する解決策は、上記の誰かが述べたように、UIコードをバックグラウンドワーカーコードから分離することです。この場合、UIスレッドでアイテムを追加するキューから取得できるインターフェイスからの入力値のみを使用しているため、これは単純です。競合状態を回避するために、キューへのアクセスを同期化する必要があります(lock(queue.Synch))。これは、Produce-Consumerデザインパターンの典型的なケースです。

于 2012-05-18T14:48:58.130 に答える
0

したがって、コードを見ると、ui update / getコードを呼び出しのみにラップすることをお勧めします。これは、現在、コードをUIスレッドで呼び出すだけで、追加のスレッドは呼び出しとスリープだけであるため、役に立たないためです。

次に例を示します。

 private Thread _thread;

    public MainWindow()
    {
        InitializeComponent();

        _thread = new Thread(DoWork);

        _thread.Start();
    }

    private void DoWork()
    {
        while (true)
        {
            var str = (string)Dispatcher.Invoke(new Func<object>(() => NotifyLabel.Content));

            str += "a";

            Dispatcher.Invoke(new Action(() => NotifyLabel.Content = str));

            Thread.Sleep(500);
        }
    }

呼び出し情報を読むことをお勧めします。ディスパッチャー/スレッドについて興味深い質問がありました。

PS:ここでmvvm / otherパターンを使用すると大幅に改善される可能性があるため、マルチスレッドコードは一般的にレビューしていません。

于 2012-05-18T14:51:04.760 に答える