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

C# 在C中,从主线程以外的线程访问COM对象速度较慢#,c#,multithreading,com,C#,Multithreading,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

我有一个专有的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
}
private void test(){
ProprietaryLib.Integers ints=ProprietaryLib.GetInts();
int x;
对于(int i=0;i<500;i++)
对于(int j=0;jtest()).Start();//时间很长
}
为什么会这样?我有没有办法加快速度?如果我使用多处理而不是多线程,那么我是否有希望获得良好的性能?(不过,听起来要复杂得多。)

更新:

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

如上所示,在一个新线程中创建和访问对象,每次访问大约12纳秒。据推测,对象实际上是在主线程上创建的,速度慢是因为从那里封送数据

如果显式地在主线程上创建数据,但在标记为单线程单元的新线程中访问数据,则访问时间会更慢,每次访问15纳秒。我想.NET一定有一些额外的开销来保持公寓的整洁,尽管我担心我不知道那是什么开销。不过,只要2-3纳秒的差距就不需要太大了


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

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

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

发件人:

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

其他参考资料
在这里如此:

MSDN:

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


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

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

换句话说,没有并发性,速度非常慢

取得进展的唯一方法是创建自己的线程,并调用其SetApartmentState()方法切换到STA,这是一个不安全的COM对象的快乐家园。您还必须在该线程上创建COM对象。您可能需要泵送一个消息循环以使其保持活动状态,这是STA要求。永远不要阻塞线程。这些都使它成为一个非线程安全类的快乐家园,如果所有调用都在一个线程上进行,那么就不会出现任何问题。您可以找到这样一个线程的示例实现


或者换句话说,当线程与非线程安全的对象一起使用时,没有免费的午餐。NET可以让你忘记在需要的地方使用锁来射击你的脚,COM可以让它自动。这样一条腿跳起来的程序员要少得多,但效率不高。

阵列足够大吗?默认STA意味着线程之间的封送。它如何确定哪个线程需要封送?也就是说,为什么它在UI线程上如此快速(意味着没有编组),而在另一个线程上却如此缓慢,即使对象是在另一个线程上创建的?我不知道它内部发生了什么,我猜它总是进行编组(根据您的结果,但没有任何其他证据)。这是一个很好的建议,但对我来说不起作用。这是一个长时间运行的进程,我不想使用UI线程。它会将一个函数放入线程池中…将发送消息以在主线程中执行test()函数…但是调用
test()
本身并不昂贵,对吗?仅
Invoke()
该部分,并在调用
Invoke()
后执行处理。您甚至可以只执行
ProprietaryLib.GetInts()在主线程上。这是一个迷人的想法。我可以试试。不确定您的语法是否正确,但它确实会导致代码运行速度很快,语法稍微得到纠正。哦,是的。。。顺便说一句,速度慢的不是GetInts(),而是访问这些ints。但是我可以把它们复制到一个简单的列表或者主线程中的某个地方,然后在lat中使用它们
private void button1_Click(object sender, EventArgs e) {
    ThreadPool.QueueUserWorkItem(delegate {
        this.Invoke((Action)(() => {
            test();
        }));
    });
}