在C#中从主线程以外的线程访问COM对象慢

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

private void test() { ProprietaryLib.Integers ints = ProprietaryLib.GetInts(); int x; for(int i = 0; i < 500; i++) for(int j = 0; j  test()).Start(); // Lots of time } 

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

更新:

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

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

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

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

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

换句话说,没有并发性和非常慢。

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

或者换句话说,当使用具有非线程安全对象的线程时,没有免费的午餐。 .NET允许你在必要时忘记使用锁定来射击你的脚,COM使它自动化。 很少有程序员以这种方式跳上一条腿,但效率不高。

它可能取决于穿线公寓模型。 如果您使用单线程单元模型(STA),则可能会遇到性能问题(如果数据大小足够大)。 如果可以(如果您没有使用需要STA的其他COM对象),您可以尝试将您的公寓模型更改为MTA(multithreading公寓模型)。

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

来自MSDN :

由于对对象的调用没有以任何方式进行序列化,因此multithreading对象并发性可提供最高性能,并为跨线程,跨进程和跨机器调用充分利用多处理器硬件。

其他参考
在这里: 你能解释一下STA和MTA吗?
MSDN: MTAThreadAttribute

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

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

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