5

我正在构建一个 WPF 应用程序,它将在后台完成一些繁重的工作。问题是当我在单元测试中运行任务时,通常需要大约 6~7 秒才能运行。但是当我在 WPF 应用程序中使用 TPL 运行它时,它需要 12 秒到 30 秒才能运行。有没有办法加快这件事。我正在调用 LogParser 的 COM api 来完成真正的工作。

更新:我调用 Log Parser API 的代码如下所示

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 线程上运行。已经有几个人提出了这个建议,但我决定检查一下,只是为了确定。MSUtil.LogQuery我安装了 LogParser SDK,下面是它在注册表中为与ProgID关联的 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 和BackgroundWorker使用 MTA 线程。这样做的结果是,当您从 TPL 任务或BackgroundWorkerCOM 运行时使用 LogParser 时,会检测到您在错误类型的线程上,并且会找到或创建一个 STA 来托管该对象。(在这种特殊情况下,它将使用所谓的“主机 STA”,这是 COM 专门为此目的创建的一个线程。在某些情况下,它会使用您的主 UI 线程,但这里不是这种情况。 )

然后,COM 会自动将来自您的工作线程的任何调用编组到该 STA 线程。它通过 Windows 消息队列执行此操作,因此对于您执行的每个方法(请记住,属性访问器只是变相的方法,因此这也适用于属性使用),您的工作线程将向该 STA 线程发送一条消息,该 STA然后,线程的消息泵必须选择该消息并发送它,此时 COM 运行时将为您调用 LogParser 上的方法。

如果您有一个涉及大量调用的 API,这会很慢。

顺便说一句,这既不是 WPF 也不是 Windows 窗体问题。这完全与使用来自非 STA 线程的基于 STA 的 COM 对象有关。如果您在其中使用非 STA 线程,您也可以使用控制台应用程序重现完全相同的问题。而且这个问题并不特定于 TPL 或BackgroundWorker- 它会影响任何使用线程池的东西,因为线程池线程都使用 MTA,而不是 STA。

解决方案是使用 STA 线程。最好的方法是创建一个专用线程。使用命名空间中的ThreadSystem.Threading来启动您自己的线程。SetApartmentState在启动它之前调用它的方法。确保从 LogParser API 创建对象实例的代码在该线程上运行,并确保您只使用该线程中的那些对象。这应该可以解决您的性能问题。

2013 年 2 月 21 日编辑澄清:

请注意,仅仅确保您使用来自 STA 线程的 COM 对象是不够的。您必须从创建它的同一 STA 线程中使用 if 。基本上,拥有 STA 模型的全部原因是使 COM 组件能够使用单线程模型。它使他们能够假设发生在他们身上的所有事情都发生在一个线程上。如果您编写使用来自多个线程的 STA 线程的多线程 .NET 代码,它将在幕后确保 COM 对象得到它想要的,这意味着所有访问都将通过它所属的线程。

这意味着,如果您从某个其他线程调用它而不是其主 STA 线程,那么即使该其他线程也恰好是 STA 线程,您仍将支付跨线程的价格。

2013 年 2 月 25 日编辑添加:

(不确定这是否与这个特定问题相关,但其他通过搜索登陆该问题的人很可能会感兴趣。)将工作转移到单独的工作线程的一个缺点是,如果您想在由于处理这些记录的任何方式,您现在都在错误的线程上。如果您使用数据绑定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 使用消息队列。我不清楚是什么决定了哪个消息队列,但我怀疑它是 shell 消息队列,因为 Delphi 调试器和 Outlook 曾经相互玩得很开心。我未经证实的假设是,进程外的 COM 服务器可能会被其他停止 shell 消息队列的东西停止。Windows 有超时来防止这种事情完全锁定系统,但它可能会导致受影响的进程大幅减速。我的解决方案是避免使用 COM。您可以通过注释掉实际使用 COM 的部分并计时该过程来检查这一点。

于 2013-02-05T04:06:24.760 回答