4

我有一个专有的 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。但是这个新线程真的安全吗?这是我认为的另一个问题的问题。

4

3 回答 3

8

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

换句话说,没有并发而且很慢。

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

或者换句话说,当使用带有非线程安全对象的线程时,没有免费的午餐。.NET 让您通过忘记在需要的地方使用锁定来射击您的脚,COM 使其自动化。以这种方式单腿跳跃的程序员要少得多,但效率不高。

于 2012-05-18T00:14:34.533 回答
2

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

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

来自MSDN

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


关于 SO 的其他参考资料:您能解释一下 STA 和 MTA 吗?
MSDN:MTAThreadAttribute

于 2012-05-17T21:20:55.103 回答
1

尝试使用以下命令在 UI 线程上执行 COM 调用Invoke()

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

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

于 2012-05-17T21:24:43.757 回答