I have been unable to figure out how to search for confirmation on this suspicion, but I am seeing evidence that a COM object created on one thread is no longer available to other threads (COM object that has been separated from its underlying RCW cannot be used) once code stops executing on the thread that created it (and that thread may have terminated). This is a really insidious problem to track down because I have calls to System.Runtime.InteropServices.Marshal.ReleaseComObject
throughout my code, but I could not identify any of them were being called causing this error. Finally I came to the conclusion that the COM object was apparently being implicitly freed when the secondary thread stopped executing. Could this be true? Is this documented behavior?

- 25,079
- 9
- 80
- 146
-
This should not be true. It's also rare that you should need to use `ReleaseComObject`. Are you sure you're using this correctly? – Michael Gunter Oct 08 '13 at 15:16
-
I'm using `ReleaseComObject` because some of the objects in the 3rd party component throw intentional exceptions if they detect that another object in the library is still in use (they don't want them running at the same time). So we have to be pretty deterministic about releasing our references in at least some cases. – BlueMonkMN Oct 08 '13 at 16:11
2 Answers
Yes, a COM object tends to have strong thread affinity. Threading is not a minor implementation detail in COM. Unlike .NET, COM provides thread-safety guarantees for a COM class. COM can publish the kind of threading it supports, with "apartment" (i.e. "not thread-safe") a very common choice. COM ensures that these requirements are met without the program having to do anything to help. Marshaling a call from one thread to another so that the object is always used in a thread-safe way is automatic. In .NET code you normally have to do this yourself, using Control.BeginInvoke or Dispatcher.BeginInvoke, for example.
An automatic consequence of this is that a thread that owns one or more COM objects that's allowed to exit will automatically get these objects released. This is necessary since there is no longer a way to meet the thread safety requirement. Trying to use them anyway after this is going to bomb. There is no cure for this beyond ensuring that the thread stays alive long enough to keep servicing these objects. Similarly, you'd need to keep the UI thread alive long enough to ensure that Dispatcher.BeginInvoke can still work in .NET.
Fwiw, yes, using Marshal.ReleaseComObject() can give you plenty of fud about this. Explicit memory management has a long history of producing buggy programs, and automatic garbage collection provided the cure. GC is quite capable of getting that COM object released without your help and never gets it wrong. It just takes a bit longer to get around to it. If you know that the COM object has unusually high resource usage that warrants releasing it deterministically then you do the exact same thing you'd do for a .NET object graph that's expensive: GC.Collect() helps that along. Check this answer for the reason why Marshal.ReleaseComObject() tends to be used unnecessarily.

- 25,079
- 9
- 80
- 146

- 922,412
- 146
- 1,693
- 2,536
-
I tried to reproduce the error is a simple program using ADODB as the sample COM server, but was unable to do so. Maybe that's because ADO is not apartment threaded. Can you provide any sample code that proves this behavior with a commonly available COM object or refer to any official documentation describing the behavior? Also, the reason I am explicitly using ReleaseComObject is because SAP (the third party provider) sample code and support engineers suggest/require it because it's "necessary" for some of their enforced-single-use objects. GC.Collect is bad form for such a use case. – BlueMonkMN Oct 08 '13 at 16:45
-
Lots of books about COM, some of them still in print. There's another victim of that SAP sample code in [this question](http://stackoverflow.com/a/3938075/17034). – Hans Passant Oct 08 '13 at 16:57
-
As it turns out, I have not fallen victim to `ReleaseComObject` misuse. The problem was in fact the implicit releasing of the object when the thread terminates as demonstrated in sample code in my other answer. – BlueMonkMN Oct 08 '13 at 17:00
-
Sure, you merely fell victim to the fud that it created :) Let's keep our eyes on the ball. – Hans Passant Oct 08 '13 at 17:02
-
Do you honestly advise calling GC.Collect everywhere a ReleaseComObject would be required (because the COM server requires that an old object be released before a new one can be created)? Wouldn't this be a serious performance concern? Isn't there *some* exception? – BlueMonkMN Oct 08 '13 at 17:09
-
No, I only recommended to do so when you *know* that the COM object has unusually high resource usage. The vast majority don't, they just take a handful of memory like a .NET object does and the normal GC cycles take care of them. If you don't know then you use the exact same tooling you use when you don't know if invisible .NET code uses a lot of resources, you use a profiler. – Hans Passant Oct 08 '13 at 17:24
-
So it sounds like you agree in this rare case I do have to call ReleaseComObject because it's not a memory issue that forces me to release the COM object. It's the COM server that throws an exception if I neglect to release the old object before creating a new one. (I can't tell the user to "please wait until .NET decides to clean up the old object before proceeding with your next activity in order to avoid a fatal error." :) ) – BlueMonkMN Oct 08 '13 at 17:43
-
The question you asked has nothing to do with either you or the GC releasing a COM object. The exception is caused by you trying to use a COM object that was already released. It isn't raised by the COM object, the CLR raises it. – Hans Passant Oct 08 '13 at 18:05
-
Right, my question doesn't relate to me or GC freeing the object, but you brought up the side-point of avoiding ReleaseComObject which I must clarify. To be clear, the *exception* I'm talking about (not related to the question) is an exception that would be thrown by SAP's COM component if you *fail* to call ReleaseComObject on object A before creating an instance of object B. So that is why in the rare case of SAP, ReleaseComObject is necessary for some of their mutually exclusive objects. Sorry I failed to clarify I was talking about a *different* exception unrelated to my original question. – BlueMonkMN Oct 08 '13 at 18:43
Here's sample code with which I managed to reproduce the behavior in Hans Passant's answer. I can click on button 1 to create an object, and then when I click on button 2 to access it after the creating thread has terminated, I get the error "COM object that has been separated from its underlying RCW cannot be used."
Public Class Form1
Dim comRef As Microsoft.Office.Interop.Outlook.Application
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Dim t As New System.Threading.Thread(AddressOf CreateApplication)
t.SetApartmentState(Threading.ApartmentState.STA)
t.Start()
End Sub
Private Sub CreateApplication()
comRef = New Microsoft.Office.Interop.Outlook.Application
End Sub
Private Sub Button2_Click(sender As Object, e As EventArgs) Handles Button2.Click
TextBox1.Text = comRef.DefaultProfileName
End Sub
End Class

- 25,079
- 9
- 80
- 146