【问题标题】:Accessing COM Object From Thread Other Than Main Thread Slow In C#在 C# 中从主线程以外的线程访问 COM 对象慢
【发布时间】:2012-05-17 21:15:01
【问题描述】:

我有一个专有的 COM 库,它返回一个整数数组(当然是它们自己的专有格式)。当我从主 UI 线程访问这个数组时,一切都很好并且运行得很快。当我从另一个线程访问它时,访问速度非常慢。下面有一些示例代码。

private void test() {
    ProprietaryLib.Integers ints = ProprietaryLib.GetInts();
    int x;
    for(int i = 0; i < 500; i++)
        for(int j = 0; j < ints.Count; j++)
            x = ints[j];
}

private void button1_Click(object sender, EventArgs e) {
    test();  // Very little time
    new System.Threading.Thread(() => test()).Start(); // Lots of time
}

为什么会这样?我有什么办法可以加快速度吗?如果我使用多处理而不是多线程,那么我是否有希望获得良好的性能? (不过,听起来要复杂得多。)

更新:

我对以下答案感到满意,但想在此处添加一些数据以供参考(我自己的和其他人的)。

如上所示在新线程中创建和访问对象每次访问大约需要 12ns。据推测,该对象实际上是在主线程上创建的,速度慢是由于从那里封送数据。

如果您在主线程上显式创建数据但在标记为单线程单元的新线程中访问它,则访问时间会更慢,每次访问为 15 ns。我想.NET 必须有一些额外的开销来保持公寓的美观,尽管我担心我不知道开销是什么。虽然只有 2-3 ns 的差异,但它不必太大。

如果您在标记为 STA 的新线程上创建和访问对象,则每次访问的时间会以 0.2ns 的速度消失。但是这个新线程真的安全吗?这是我认为的另一个问题的问题。

【问题讨论】:

  • 数组够大吗?默认 STA 意味着线程之间的编组。

标签: c# multithreading com


【解决方案1】:

COM 对象具有线程亲和性,它们可以告诉 COM 它们不是线程安全的。使用注册表中的一个键,即“ThreadingModel”键。绝大多数都这样做,要么指定“公寓”,要么只是省略键。它在 .NET 中不太明确,它使用 MSDN 告诉您类不是线程安全的,并且不会提醒您忘记阅读这篇文章。绝大多数 .NET 类都不是线程安全的,与 COM coclass 没有什么不同。与 .NET 不同,COM 确保以线程安全的方式调用它们。通过自动封送对创建对象的线程的调用。

也就是说,没有并发而且很慢。

取得成功的唯一方法是创建您自己的线程并调用其 SetApartmentState() 方法来切换到 STA,这是一个非线程安全的 COM 对象的快乐家园。您还必须在该线程上创建 COM 对象。而且您可能必须抽出一个消息循环以使其保持活动状态,这是 STA 的要求。并且永远不要阻塞线程。这些东西使它成为一个非线程安全的类的幸福之家,如果所有调用都在 one 线程上进行,那么就不会出错。您可以找到此类线程in this answer 的示例实现。

或者换句话说,当使用带有非线程安全对象的线程时,没有免费的午餐。 .NET 让您通过忘记在需要的地方使用 lock 来开枪,COM 使其自动化。这样做的程序员少得多,但效率不高。

【讨论】:

  • 所以,如果我编写一个单线程控制台程序并且它使用 STA COM 对象,它会进行大量处理,因此不会发送消息循环,而且它可能正在等待来自系统中的其他地方,所以它时不时地阻塞,这可以吗?
  • 如果您有时间,请在此链接查看我的下一个问题,并告诉我我所有的许多误解:stackoverflow.com/questions/10654098/…
【解决方案2】:

这可能取决于线程单元模型。如果您使用单线程单元模型 (STA),您可能会遇到性能问题(如果数据大小足够大)。如果可以(如果您没有使用需要 STA 的另一个 COM 对象),您可以尝试将您的单元模型更改为 MTA(多线程单元模型)。

注意:WinForms 与 MTA 兼容,它总是检查单元模型是单线程的,因为它使用的某些 COM 对象(例如剪贴板和拖放)需要它。 我从未尝试过,但如果你不使用这些功能,也许它会起作用。

来自MSDN

因为对对象的调用不以任何方式进行序列化,所以多线程对象并发提供了最高的性能,并充分利用了多处理器硬件进行跨线程、跨进程和跨机器调用。

其他参考资料
在这里:Could you explain STA and MTA?
MSDN:MTAThreadAttribute

【讨论】:

  • 它如何确定哪个线程需要编组?也就是说,为什么它在 UI 线程上这么快(意味着没有编组)但在另一个线程上却很慢,即使对象是在另一个线程上创建的?
  • 我不知道里面发生了什么,我它总是进行编组(根据你的结果但没有任何其他证据)。
【解决方案3】:

尝试使用Invoke() 在 UI 线程上执行 COM 调用:

private void button1_Click(object sender, EventArgs e) {
    ThreadPool.QueueUserWorkItem(delegate {
        this.Invoke((Action)(() => {
            test();
        }));
    });
}

在调用 Invoke() 之前和之后执行其余的长时间运行操作,以便只有快速 COM 调用在 UI 线程中运行。此外,根据您的操作,您可能会消除很多括号和其他线路噪音。

【讨论】:

  • 一个很好的建议,但它对我不起作用。这是一个长期运行的过程,我想远离 UI 线程。
  • 它将在线程池中排队一个函数...将发送一条消息以在主线程中执行 test() 函数...
  • 但是调用test() 本身并不昂贵,对吧?只有Invoke()那部分,并在调用Invoke()之后执行你的处理。你甚至可以在主线程上执行ProprietaryLib.GetInts();
  • 这是一个迷人的想法。我可以试试。不确定您的语法是否完全正确,但确实会导致该代码运行得很快,语法略有修正。
  • 哦,是的...顺便说一下,实际上并不是 GetInts() 很慢,它正在访问这些整数。但我也许可以将它们复制到一个简单的 List 或主线程中的某个东西,然后稍后再使用它们……现在研究一下。
猜你喜欢
  • 1970-01-01
  • 2011-10-19
  • 2014-12-27
  • 1970-01-01
  • 1970-01-01
  • 2011-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多