5

私は、バックグラウンドでいくつかの重い作業を行うWPFアプリを構築しています。問題は、単体テストでタスクを実行すると、通常、実行に約6〜7秒かかることです。しかし、WPFアプリでTPLを使用して実行すると、実行に12〜30秒かかります。このことをスピードアップする方法はありますか?実際の作業を行うために、LogParserのCOMAPIを呼び出しています。

更新:LogParserAPIを呼び出すための私のコードは次のようになります

var thread = new Thread(() =>
            {
                var logQuery = new LogQueryClassClass();
                var inputFormat = new COMEventLogInputContextClassClass
                {
                    direction = "FW",
                    fullText = true,
                    resolveSIDs = false,
                    formatMessage = true,
                    formatMsg = true,
                    msgErrorMode = "MSG",
                    fullEventCode = false,
                    stringsSep = "|",
                    iCheckpoint = string.Empty,
                    binaryFormat = "HEX"
                };
                try
                {
                    Debug.AutoFlush = true;
                    var watch = Stopwatch.StartNew();
                    var recordset = logQuery.Execute(query, inputFormat);
                    watch.Stop();

                    watch = Stopwatch.StartNew();
                    while (!recordset.atEnd())
                    {
                        var record = recordset.getRecord();
                        recordProcessor(record);
                        recordset.moveNext();
                    }
                    recordset.close();
                    watch.Stop();
                }
                catch
                {
                }
                finally
                {
                    if (logQuery != null)
                    {
                        Marshal.ReleaseComObject(logQuery);
                        GC.SuppressFinalize(logQuery);
                        logQuery = null;
                    }
                }
            });
        thread.SetApartmentState(ApartmentState.STA);
        thread.Start();
        thread.Join();

現在のところ、この変更により、デバッグモードで約3〜4秒の改善が見られますが、Ctrl + F5を押して実行すると、私をはるかに超えています。どうして??

4

2 に答える 2

10

ここでの問題は、使用しているCOMオブジェクトがSTAスレッドでのみ実行されることです。すでに何人かの人がこれを提案しましたが、念のために確認することにしました。LogParser SDKをインストールしました。これは、MSUtil.LogQueryProgIDに関連付けられたCLSIDのレジストリに配置されるものです。

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{8CFEBA94-3FC2-45CA-B9A5-9EDACF704F66}]
@="LogQuery"
"AppID"="{3040E2D1-C692-4081-91BB-75F08FEE0EF6}"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{8CFEBA94-3FC2-45CA-B9A5-9EDACF704F66}\InprocServer32]
@="C:\\Program Files (x86)\\Log Parser 2.2\\LogParser.dll"
"ThreadingModel"="Apartment"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{8CFEBA94-3FC2-45CA-B9A5-9EDACF704F66}\ProgID]
@="MSUtil.LogQuery.1"

[HKEY_CLASSES_ROOT\Wow6432Node\CLSID\{8CFEBA94-3FC2-45CA-B9A5-9EDACF704F66}\VersionIndependentProgID]
@="MSUtil.LogQuery"

それ"ThreadingModel"="Apartment"がクリンチャーです。このCOMクラスは、STAスレッドでのみ実行できることを宣言しています。

TPLとBackgroundWorkeruseMTAスレッドの両方。この結果、TPLタスクまたはBackgroundWorkerCOMランタイムのいずれかからLogParserを使用すると、間違った種類のスレッドを使用していることが検出され、オブジェクトをホストするSTAが検出または作成されます。(この特定のケースでは、COMがこの目的のために特別に作成するスレッドである「ホストSTA」と呼ばれるものを使用します。代わりにメインUIスレッドを使用するシナリオもありますが、ここではそうではありません。 )。

次に、COMは、ワーカースレッドからそのSTAスレッドへの呼び出しを自動的にマーシャリングします。これはWindowsメッセージキューを介して行われるため、実行するメソッドごとに(プロパティアクセサーは偽装されたメソッドにすぎないため、これはプロパティの使用にも当てはまります)、ワーカースレッドはそのSTAスレッドであるSTAにメッセージを送信します。次に、スレッドのメッセージポンプはそのメッセージを取得してディスパッチする必要があります。その時点で、COMランタイムはLogParserのメソッドを呼び出します。

大量の呼び出しを伴うAPIがある場合、これは遅くなります。

ちなみに、これはWPFでもWindowsフォームの問題でもありません。それは完全に非STAスレッドからのSTAベースのCOMオブジェクトの使用と関係があります。STA以外のスレッドを使用している場合は、コンソールアプリでもまったく同じ問題を再現できます。また、問題はTPLにも固有のものでもありBackgroundWorkerません。スレッドプールスレッドはすべてSTAではなくMTAを使用するため、スレッドプールを使用するものすべてに影響を及ぼします。

解決策は、STAスレッドを使用することです。そのための最良の方法は、専用のスレッドを作成することです。Thread名前空間のクラスを使用してSystem.Threading、独自のスレッドを起動します。SetApartmentState開始する前にそのメソッドを呼び出します。LogParser APIからオブジェクトのインスタンスを作成するコードがそのスレッドで実行されていることを確認し、そのスレッドからそれらのオブジェクトのみを使用することも確認してください。これにより、パフォーマンスの問題が修正されます。

明確にするために2013年2月21日編集:

STAスレッドからCOMオブジェクトを使用していることを確認するだけでは不十分であることに注意してください。作成したのと同じSTAスレッドからの場合は使用する必要があります。基本的に、STAモデルを使用する理由は、COMコンポーネントがシングルスレッドモデルを使用できるようにするためです。これにより、発生するすべてのことが1つのスレッドで発生すると想定できます。複数のスレッドからのSTAスレッドを使用するマルチスレッド.NETコードを作成する場合、COMオブジェクトが必要なものを確実に取得するようになります。つまり、すべてのアクセスは、それが属するスレッドを経由します。

つまり、ホームSTAスレッド以外のスレッドから呼び出した場合、その他のスレッドもSTAスレッドであったとしても、クロススレッドの料金を支払うことになります。

2013年2月25日編集して以下を追加。

(これがこの特定の質問に関連するかどうかはわかりませんが、検索を通じてこの質問にたどり着く他の人にとっては興味深いかもしれません。)作業を別のワーカースレッドに移動することの欠点は、UIを更新する場合です。いずれにせよ、これらのレコードを処理した結果、間違ったスレッドになりました。データバインディングを使用している場合INotifyPropertyChanged、WPFはスレッド間の変更通知を自動的に処理しますが、これはパフォーマンスに重大な影響を与える可能性があります。バックグラウンドスレッドで多くの作業を行う必要があるが、その作業で最終的にUIを更新する必要がある場合は、それらの更新をバッチ処理する手順を実行する必要があります。完全に些細なことではありません-ここから始まる一連のブログエントリを参照してください:http://www.interact-sw.co.uk/iangblog/2013/02/14/wpf-async-too-fast

于 2013-02-20T08:58:17.870 に答える
2

COMはIPCにメッセージキューを使用します。何がどのメッセージキューを決定するのかはわかりませんが、DelphiデバッガとOutlookが互いに陽気なプレイをしていたため、シェルメッセージキューだと思います。私の証明されていない仮説は、プロセス外のCOMサーバーが、シェルメッセージキューを停止する他の何かによって停止する可能性があるというものです。Windowsには、この種のものがシステムを完全にロックするのを防ぐためのタイムアウトがありますが、影響を受けるプロセスで大幅な速度低下を引き起こす可能性があります。私の解決策は、COMを回避することです。これは、実際にCOMを使用している部分をコメントアウトし、プロセスのタイミングを調整することで確認できます。

于 2013-02-05T04:06:24.760 に答える