c# - 如何提高WPF中后台任务的性能?

标签 c# wpf com task-parallel-library

我正在构建一个WPF应用程序,它将在后台做一些繁重的工作。问题是,当我在单元测试中运行任务时,通常需要6到7秒钟才能运行。但是当我在WPF应用程序中使用TPL运行它时,它需要大约12s〜30s的时间才能运行。有没有办法加快这件事。我正在调用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();

现在的变化就是,在 Debug模式下我可以看到大约3-4s的改进,但是当我按 Ctrl + F5 来运行它时却没有,这远远超出了我的范围。怎么来的??

最佳答案

这里的问题是您使用的COM对象将仅在STA线程上运行。已经有几个人提出了这个建议,但是为了确定,我决定进行检查。我安装了LogParser SDK,这是它放入与MSUtil.LogQuery 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任务或BackgroundWorker使用LogParser时,COM运行时将检测到您使用的是错误的线程,并将查找或创建STA来承载对象。 (在这种情况下,它将使用所谓的“主机STA”,这是COM为此目的专门创建的线程。在某些情况下,它将使用您的主UI线程来代替,但是这里不是这种情况。 )

然后,COM会自动编码(marshal)从您的工作线程到该STA线程的所有调用。它通过Windows消息队列执行此操作,因此对于您执行的每个方法(记住,属性访问器只是伪装的方法,因此这也适用于属性使用),您的工作线程将向该STA线程(该STA)发送一条消息然后,线程的消息泵必须拾取该消息并将其分派(dispatch),这时COM运行时将为您调用LogParser上的方法。

如果您的API涉及大量的调用,这将很慢。

顺便说一下,这既不是WPF也不是Windows Forms问题。这完全与使用来自非STA线程的基于STA的COM对象有关。如果您在控制台应用程序中使用了非STA线程,也可以重现同样的问题。而且问题不是TPL或BackgroundWorker特有的-它会困扰使用线程池的任何事物,因为线程池线程全部使用MTA,而不是STA。

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

编辑于2013年2月21日以澄清:

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

这意味着,如果您从其本地STA线程以外的其他线程调用它,那么即使该其他线程也恰好是STA线程,您仍将付出跨线程的代价。

编辑于2013年2月25日添加:

(不知道这是否与该特定问题有关,但其他人可能会通过搜索找到感兴趣的问题。)将工作移至单独的工作程序线程的不利之处在于,如果要在以下位置更新UI,请执行以下操作:由于处理这些记录的任何方式,您现在处在错误的线程上。如果您使用数据绑定(bind)INotifyPropertyChanged,则WPF将自动为您处理跨线程更改通知,但这可能会对性能产生重大影响。如果您需要在后台线程上做很多工作,但是最终需要更新UI,那么您可能需要采取步骤来批量处理这些更新。这并非完全无关紧要-请参阅从此处开始的一系列博客文章:http://www.interact-sw.co.uk/iangblog/2013/02/14/wpf-async-too-fast

关于c# - 如何提高WPF中后台任务的性能?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/14699950/

相关文章:

c# - X 交互后使用 TcpClient(套接字或流)通过网络发送和接收失败

c# - 存在从 'float' 和 'float' 以及从 'float' 到 'float' 的隐式转换

c# - 制作要从 c# 调用的 COM dll,HRESULT 错误处理?

c# - 通过 COM 将复杂信息从 Delphi 代码传递到 C#

c++ - x64 程序可以访问 Windows x64 上的 x86 COM 对象吗?

C# 将用户输入传递给方法,并输出结果(来自主函数)?

c# - 将 NEST (Elasticsearch) 搜索结果转换为数据集 - C#

wpf - 使用枚举值在组合框中添加其他项目

wpf - 依赖属性不起作用,尝试通过样式 setter 进行设置

c# - 更新 viewModel 中的属性