4

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
  • Is the array big enough? Default STA implies marshaling between threads. Commented May 17, 2012 at 21:18

3 Answers 3

8

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.

Sign up to request clarification or add additional context in comments.

2 Comments

So, if I write a single threaded console program and it uses STA COM objects, and it does a lot of processing and so isn't pumping a message loop, and also maybe it's waiting on messages from elsewhere in the system so it blocks now and then, is that okay or not?
If you've got time, check out my next question at this link and let me know about all my many misconceptions: stackoverflow.com/questions/10654098/…
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

2 Comments

How does it determine which thread requires marshalling? That is, why is it so quick on the UI thread (implying no marshalling) but slow on the other thread, even though the object was created on that other thread?
I don't know what happens inside it, I guess it always does marshaling (according with your results but without any other evidence).
1

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.

8 Comments

A good suggestion, but it won't work for me. It's a long running process I want to keep off the UI thread.
It'll enqueue a function in the thread pool that...will dispatch a message to execute the test() function back in the main thread...
But the call to test() itself is not expensive, right? Only Invoke() that part, and perform your processing after the call to Invoke(). You could even just execute ProprietaryLib.GetInts(); on the main thread.
That is a fascinating idea. I may try that. Not sure your syntax is quite right, but it does result in that code running quickly, with slightly corrected syntax.
Oh yeah... by the way it's not actually GetInts() that's slow, it's accessing those ints. But I could maybe copy them to a simple List<int> or something in the main thread and then use them later... looking into that now.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.