Accessing COM Object From Thread Other Than Main Thread Slow In C#

时光毁灭记忆、已成空白 提交于 2019-12-10 16:45:19

问题


I have a proprietary COM library that returns an array of integers (in their own proprietary format of course). When I access this array from the main UI thread, all is well and runs quickly. When I access it from another thread, the access is very slow. There's some sample code below.

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
}

Why is this happening? Is there any way for me to speed this up? If I use multi-processing instead of multi-threading, would I then have some hope of getting good performance? (Ugh though, sounds a lot more complicated.)

UPDATE:

I'm satisfied with the answers below, but wanted to add some data here for reference (my own and anyone else's).

Creating and accessing object in a new thread as shown above gives about 12ns per access. Presumably the object is actually created on the main thread and the slow speed is due to marshaling the data from there.

If you explicitly create the data on the main thread but access it in a new thread marked as a single threaded apartment, access time is even slower, at 15 ns per access. I guess .NET must have some extra overhead to keep the apartment nice, though it worries me that I don't know what that overhead is. With just a 2-3 ns difference it wouldn't have to be much though.

If you create and access the object on a new thread marked STA the time melts away at .2ns per access. But is this new thread really safe? That's a question for another question I think.


回答1:


COM objects have threading affinity, they can tell COM that they are not thread-safe. With a key in the registry, the "ThreadingModel" key. The vast majority do, either by specifying "Apartment" or just omitting key. It is less explicit in .NET, it uses MSDN to tell you that classes are not thread-safe and doesn't otherwise remind you that you forgot to read the article. The vast majority of .NET classes are not thread-safe, no different from COM coclasses. Unlike .NET, COM makes sure that they get called in a thread-safe way. By automatically marshaling the call to the thread that created the object.

In other words, no concurrency and very slow.

The only way to get ahead is to create your own Thread and call its SetApartmentState() method to switch to STA, a happy home for a COM object that isn't thread-safe. And you have to create the COM object on that thread as well. And you may have to pump a message loop to keep it alive, an STA requirement. And never block the thread. These are the things that make it a happy home for a class that is not thread-safe, if all the calls are made on one thread then nothing can go wrong. You can find a sample implementation of such a thread in this answer.

Or in other words, there is no free lunch when using threads with objects that are not thread-safe. .NET lets you shoot your foot by forgetting to use lock where needed, COM makes it automatic. A lot less programmers hopping on one leg that way, but not as efficient.




回答2:


It may depends on the threading apartment model. If you're using a single thread apartment model (STA) you may suffer of performance issues (if the data size is big enough). If you can (then if you're not using another COM object that needs STA) you may try to change your apartment model to MTA (multithreaded apartment model).

Note: WinForms is not compatible with MTA, it always checks the apartment model is single threaded because is required by some of the COM objects it uses (Clipboard and Drag & Drop, for example). I never tried but if you do not use that features maybe it works.

From MSDN:

Because calls to objects are not serialized in any way, multithreaded object concurrency offers the highest performance and takes the best advantage of multiprocessor hardware for cross-thread, cross-process, and cross-machine calling.

Additional references
Here on SO: Could you explain STA and MTA?
MSDN: MTAThreadAttribute




回答3:


Try performing the COM call on the UI thread using Invoke():

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

Do the rest of your long-running operation before and after this call to Invoke(), so that only the quick COM call runs in the UI thread. Also, depending on what you are doing, you can probably get rid of a lot of the brackets and other line noise.



来源:https://stackoverflow.com/questions/10643652/accessing-com-object-from-thread-other-than-main-thread-slow-in-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!