6

I'm currently looking at a problem we are having in a .Net Framework 2.0 windows service (C#) that has X number of MTA threads running that access COM components. Each thread initializes it's own instance of the com component object. The com component object does not have any UI elements. It's simply business logic that communicates with a sql server database and a C# dll with a com interface that in turn does socket communication and access to the same sql server database.

Through my research I've found that you should not be instantiating STA COM components on an MTA thread but I can't find any specific text to say what the dangers of this are or maybe it's the fact I don't understand COM threading apartments that well.

Would there be concurrency problems with the model being described above? Even if each MTA thread is creating it's own STA COM object?

Edit

The problem we are actually running into is an object reference not set to instance of an object error in the setter of the connection string in following code block. This occurs within the C# COM object that is called by the c++ COM object:

IDbConnection connection;

//Code omitted for brevity where connection is initialized

connection.ConnectionString = myConnectionString;

Exception Type: System.NullReferenceException Message: Object reference not set to an instance of an object. Data: System.Collections.ListDictionaryInternal TargetSite: Void ConnectionString_Set(System.String) at System.Data.OracleClient.OracleConnection.ConnectionString_Set(String value) at System.Data.OracleClient.OracleConnection.set_ConnectionString(String value)

Cole W
  • 15,123
  • 6
  • 51
  • 85
  • All those objects live on the same thread - an STA thread COM runtime has created for that very purpose the first time you've created an STA component in an MTA apartment. Any call to any of those components gets marshaled to that STA thread, executed there (in first-come-first-served fashion), and any results marshaled back to the caller's MTA thread. Whether this loss of concurrency is a problem in your design, is for you to decide. – Igor Tandetnik Dec 01 '13 at 04:13
  • I was more curious about true concurrency issues with weird things happening because of threads stomping on each other in terms of memory. The lack of concurrency is not an issue at this point. I just want to make sure the program doesn't crash. – Cole W Dec 01 '13 at 13:40
  • The problem is most likely somewhere in the "code omitted for brevity". `connection` is null at the time you attempt to assign to its `ConnectionString` property, so it was not, after all, initialized, your assurances to the contrary notwithstanding. Figure out why. – Igor Tandetnik Dec 01 '13 at 19:26
  • 1
    There isn't that much really. I'll post it. Connection is not null. I've verified it isn't. Just an fyi it seems it was related to running those threads in MTA and not STA. Since I've made this change it's no longer throwing this error. – Cole W Dec 01 '13 at 20:35
  • You have to be careful with instantiating COM objects on STA threads that you control -- once the STA thread exits, those COM objects are gone -- so if another thread tries to access them, you can run into an issue where the "COM object has been separated from its underlying RCW and cannot be used". – BrainSlugs83 Jul 08 '14 at 08:16

1 Answers1

13

There are four basic "dangers" when making calls from the MTA:

  • the COM object is apartment threaded, very common, and the call must thus be marshaled to the apartment that owns the object. The technically correct wording for "STA COM". That is expensive, marshaled calls to small methods are usually 10,000x times slower.

  • the COM object doesn't support a proxy to marshal the call. That's very easy to find out, the call will fail with E_NOINTERFACE.

  • the client programmer doesn't realize that he's making a call from the MTA and forgets to marshal the interface pointer. COM cannot prevent this on calls made to an in-process COM server. You can't make this mistake in a .NET program, the CLR will always marshal, but easy to do in other runtime environments. This otherwise invokes the common wrath of making thread-unsafe calls, works when you debug the code, fails randomly in production and is impossible to debug.

  • the COM author has published his component to be compatible with MTA by declaring its ThreadingModel as Both or Free. But did not actually test his code thoroughly enough, writing thread-safe code is notoriously difficult. Especially dangerous in [ComVisible] .NET classes because they are automatically registered as Both and it is very easy to completely forget to test the code against that promise.

Your snippet is way too inscrutable to make the call but a possible candidate for the 4th bullet. It doesn't otherwise look like any COM is involved at all. Data providers like Oracle's are normally free threaded and tested thoroughly by hundreds of thousands of programmers.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536