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.

user12861
  • 2,358
  • 4
  • 23
  • 41

3 Answers3

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.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • 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? – user12861 May 18 '12 at 01:40
  • If you've got time, check out my next question at this link and let me know about all my many misconceptions: http://stackoverflow.com/questions/10654098/is-sta-message-loop-required-in-this-case – user12861 May 18 '12 at 14:03
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

Community
  • 1
  • 1
Adriano Repetti
  • 65,416
  • 20
  • 137
  • 208
  • 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? – user12861 May 17 '12 at 21:23
  • I don't know what happens inside it, I _guess_ it always does marshaling (according with your results but without any other evidence). – Adriano Repetti May 17 '12 at 21:32
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.

We Are All Monica
  • 13,000
  • 8
  • 46
  • 72
  • A good suggestion, but it won't work for me. It's a long running process I want to keep off the UI thread. – user12861 May 17 '12 at 21:27
  • It'll enqueue a function in the thread pool that...will dispatch a message to execute the test() function back in the main thread... – Adriano Repetti May 17 '12 at 21:27
  • 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. – We Are All Monica May 17 '12 at 21:29
  • 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. – user12861 May 17 '12 at 21:31
  • 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 or something in the main thread and then use them later... looking into that now. – user12861 May 17 '12 at 21:32
  • With that call you still execute test() in the main thread (enqueue to the pool then enqueue back to the main thread). – Adriano Repetti May 17 '12 at 21:34
  • Moreover idea to split the function in two threads, if it works, is good and sounds nice! – Adriano Repetti May 17 '12 at 21:35
  • Well... a good effort, but of course marshalling the data myself using this technique was even slower than letting .NET do it for me. – user12861 May 17 '12 at 22:02