我有一个专有的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的新线程上创建并访问该对象,则每次访问的时间会消失.2ns。但这个新线程真的安全吗?这是我想到的另一个问题的问题。
答案 0 :(得分:8)
COM对象具有线程关联性,它们可以告诉COM它们不是线程安全的。使用注册表中的键,“ThreadingModel”键。绝大多数都是通过指定“公寓”或只是省略键。它在.NET中不太明确,它使用MSDN告诉您类不是线程安全的,并且不会提醒您忘记阅读文章。绝大多数.NET类都不是线程安全的,与COM coclass没有区别。与.NET不同,COM确保以线程安全的方式调用它们。通过自动将调用封送到创建对象的线程。
换句话说,没有并发性和非常慢。
获得成功的唯一方法是创建自己的Thread并调用其SetApartmentState()方法切换到STA,这是一个非线程安全的COM对象的幸福家园。而且你还必须在该线程上创建COM对象。并且您可能必须抽取消息循环以使其保持活动,这是STA的要求。永远不会阻止线程。如果所有调用都是在一个线程上进行的,则不会出现任何问题,这些使得它成为一个非线程安全的类的幸福之家。您可以找到此类线程in this answer的示例实现。
或者换句话说,当使用非线程安全对象的线程时,没有免费的午餐。 .NET允许你在必要时忘记使用 lock 来射击你的脚,COM使它自动化。很少有程序员以这种方式跳上一条腿,但效率不高。
答案 1 :(得分:2)
这可能取决于穿线公寓模型。如果您使用的是单线程单元模型(STA),则可能会遇到性能问题(如果数据大小足够大)。如果可以(那么如果您没有使用需要STA的其他COM对象),您可以尝试将您的公寓模型更改为MTA(多线程公寓模型)。
注意:WinForms 不与MTA兼容,它总是检查公寓模型是单线程的,因为它使用的某些COM对象需要它(例如Clipboard和Drag&amp; Drop) 。 我从未尝试过,但如果你不使用这些功能,那么它可能有用。
来自MSDN:
由于对对象的调用没有以任何方式进行序列化,因此多线程对象并发性可提供最高性能,并为多线程,跨进程和跨机器调用充分利用多处理器硬件。
其他参考
在这里SO:Could you explain STA and MTA?
MSDN:MTAThreadAttribute
答案 2 :(得分:1)
尝试使用Invoke()
:
private void button1_Click(object sender, EventArgs e) {
ThreadPool.QueueUserWorkItem(delegate {
this.Invoke((Action)(() => {
test();
}));
});
}
在调用Invoke()
之前和之后执行剩余的长时间运行操作,以便只在UI线程中运行快速COM调用。另外,根据你正在做的事情,你可以摆脱很多括号和其他线路噪音。