Events and threads

J

JohnM

Hi there,

Are there any specific rules or best-practices I should be aware regarding
events and the threads they're fired on. Object 1 can be created on thread 1
for instance and an event then registered with it on thread 2. Object 1
might then fire the event on thread 3 and the event might later be
deregistered on thread 4. Are there any inherent problems with these (and
other) scenarios so long as the event handler is properly dealing with its
own synchronization issues. Note that I'm asking because I recently received
the exception:

"COM object that has been separated from its underlying RCW cannot be used"

This apparently occurred because I was registering and deregestering my
handler for a (native Visual Studio) event on thread 2 even though the
object firing the event was created on thread 1 (and firing on thread 1). It
even causes VS itself to consistently crash but I could later see that it
had something to do with some underlying calls to "Advise()" and
"Unadvise()" (for those familiar with COM). Apparently I must be breaking
the thread affinity rules since the problem disappears if I register and
unregister my handler on thread 1 instead. Any insight would be appreciated.
Thanks.
 
N

Nicholas Paldino [.NET/C# MVP]

JohnM,

COM has its own thread affinity rules (refered to as apartments) which
require some special handling.

You have to be aware of the apartment state required by the COM object.
For a good majority of them, they require an STA apartment, meaning that
they are attuned to the thread that they are created on, and calls are
marshaled back into that thread.

Now, with event handlers, you are trying to access that STA object on
another thread. When making calls to other components in other threads, you
have to marshal the call correctly, which usually involves a call to
CoMarshalInterThreadInterfaceInStream (a COM API call) so that you can
correctly marshal the call between apartments and threads. I would be
willing to bet that this is the case here.

The easy solution is to make sure that you make all the calls to the
component on the thread that they are called in. If you need to make the
calls on other threads, then you are probably going to have to use the
unmanaged CoMarshalInterThreadInterfaceInStream and
CoGetInterfaceAndReleaseStream API methods to correctly marshal the
interface to the other threads to call. Also, you have to make sure that
those threads are initialized for COM (using the SetApartmentState method on
the Thread, which could be an issue if you are using ThreadPool threads,
since the apartment state can only be set once and the thread will
eventually be used for other purposes).

Even if you do marshal the interface pointers correctly, the calls, when
made on another thread, will still be marshaled to the main thread (assuming
it is an STA component, which I am), so unless you are doing considerable
other work on those threads, it won't be worth it.
 
J

JohnM

COM has its own thread affinity rules (refered to as apartments) which
require some special handling.

You have to be aware of the apartment state required by the COM object.
For a good majority of them, they require an STA apartment, meaning that
they are attuned to the thread that they are created on, and calls are
marshaled back into that thread.

Now, with event handlers, you are trying to access that STA object on
another thread. When making calls to other components in other threads,
you have to marshal the call correctly, which usually involves a call to
CoMarshalInterThreadInterfaceInStream (a COM API call) so that you can
correctly marshal the call between apartments and threads. I would be
willing to bet that this is the case here.

The easy solution is to make sure that you make all the calls to the
component on the thread that they are called in. If you need to make the
calls on other threads, then you are probably going to have to use the
unmanaged CoMarshalInterThreadInterfaceInStream and
CoGetInterfaceAndReleaseStream API methods to correctly marshal the
interface to the other threads to call. Also, you have to make sure that
those threads are initialized for COM (using the SetApartmentState method
on the Thread, which could be an issue if you are using ThreadPool
threads, since the apartment state can only be set once and the thread
will eventually be used for other purposes).

Even if you do marshal the interface pointers correctly, the calls,
when made on another thread, will still be marshaled to the main thread
(assuming it is an STA component, which I am), so unless you are doing
considerable other work on those threads, it won't be worth it.

Thanks for the info. I'm very familiar with the COM issues you mentioned but
(obviously) have much less .NET experience (my background is C++ and the
WinAPI). The object firing the event in this case is a native member of the
Visual Studio extensibility API which is used for writing VS AddIns (which
I'm now working on). I'm working with the .NET classes directly however so
even if the underyling objects are COM objects, I would have thought that
all the marshalling would have been handled by .NET (since the underlying
COM objects are implementation details which I shouldn't even be aware of).
Apparently not so I have no (practical) choice but to register/unregister my
events on the same thread the object firing the event is created on. This
makes things more difficult noting that these objects are handed to me by VS
on the main thread so I have no say in when they're created (but I later
need to register my events on another thread). In any case, is this normal
behaviour. That is, can you normally register/unregister a .NET event on a
different thread than the object firing the event is created on. If so then
how is someone supposed to know if it's really safe or not given this
experience (i.e., the error mentioned in my first post happens most of the
time but not always - in theory it might not have surfaced until the program
was actually on a customer's machine). Thanks again.
 
N

Nicholas Paldino [.NET/C# MVP]

John,

With .NET objects, you can register/unregister event handlers on any
thread, really. Granted, there might be some custom code on the publisher
of the event which might be buggy (due to custom add/remove handlers which
are buggy), but generally, the documentation should say whether or not it is
thread safe.

For COM objects, the reason why it doesn't work on other threads is (as
I think you know) because you have not set up the apartment on the other
threads correctly. When you add/remove an event on a COM object, it
marshals the call to the Advise/Unadvise method on the connection point, and
since that is a method on a COM interface, you need to marshal the call
correctly (which doesn't happen when you pass COM object wrappers across
thread boundaries in .NET). You simply have to know it is a COM object when
dealing with it, and marshal it appropriately.
 
J

JohnM

With .NET objects, you can register/unregister event handlers on any
thread, really. Granted, there might be some custom code on the publisher
of the event which might be buggy (due to custom add/remove handlers which
are buggy), but generally, the documentation should say whether or not it
is thread safe.

For COM objects, the reason why it doesn't work on other threads is (as
I think you know) because you have not set up the apartment on the other
threads correctly. When you add/remove an event on a COM object, it
marshals the call to the Advise/Unadvise method on the connection point,
and since that is a method on a COM interface, you need to marshal the
call correctly (which doesn't happen when you pass COM object wrappers
across thread boundaries in .NET). You simply have to know it is a COM
object when dealing with it, and marshal it appropriately.

Ok, thanks (and yes, I do know this material having done a lot of ATL once
upon a time). I'll just have to swallow that it may be a bug given that .NET
should be marshalling things correctly assuming there are no problems in the
underlying COM objects themselves (which there appear to be in this case).
In any "event", thanks again.
 
N

Nicholas Paldino [.NET/C# MVP]

John,

It's not a bug, it's just that it is expected that the user set up the
apartment state correctly, and manage the interface pointers when
transferring them across threads. The same rules that applied in COM apply
in .NET as well when using COM, and it is up to the developer to handle that
situation. It is not automatically done for you.
 
J

JohnM

It's not a bug, it's just that it is expected that the user set up the
apartment state correctly, and manage the interface pointers when
transferring them across threads. The same rules that applied in COM
apply in .NET as well when using COM, and it is up to the developer to
handle that situation. It is not automatically done for you.

Perhaps you're right. However, from the programmer's perspective COM doesn't
come into play here. It's .NET we're working with and COM is rarely ever
mentioned in the .NET literature (for the extensibility API). For someone
with no background in COM in particular, or who has never even heard of
marshalling, it would make no difference to them. There's really just an
assumption that all the plumbing is handled automatically. Even in my own
case, with many years of experience, I wasn't sure if I was doing something
wrong or if there was really a bug. While I knew early on there was a COM
marshalling problem, this is something that many others wouldn't have
recognized without a COM background. Nevertheless, it was still unclear
whether I should be expeirncing it given that .NET was in the driver's seat.
I'll just have to do some more reading on the subject now that you've
enlightened me. Thanks again.
 
A

Arnshea

John,

It's not a bug, it's just that it is expected that the user set up the
apartment state correctly, and manage the interface pointers when
transferring them across threads. The same rules that applied in COM apply
in .NET as well when using COM, and it is up to the developer to handle that
situation. It is not automatically done for you.

--
- Nicholas Paldino [.NET/C# MVP]
- (e-mail address removed)




Ok, thanks (and yes, I do know this material having done a lot of ATL once
upon a time). I'll just have to swallow that it may be a bug given that
.NET should be marshalling things correctly assuming there are no problems
in the underlying COM objects themselves (which there appear to be in this
case). In any "event", thanks again.- Hide quoted text -

- Show quoted text -

The ".NET Framework Developer's Guide", section "Managed and Unmanaged
Threading" leads me to believe that this should be handled
automatically. The CLR "creates and initializes an apartment when
calling a COM object. A managed thread can create and enter a single-
threaded apartment (STA) that contains only one thread, or a multi-
threaded apartment (MTA) that contains one or more threads. When a COM
apartment and a thread-generated apartment are compatible, COM allows
the calling thread to make calls directly to the COM object. If the
apartments are incompatible, COM creates a compatible apartment and
marshals all calls through a proxy in the new apartment."

Based on the value of
System.Threading.Thread.CurrentThread.ApartmentState, the CLR will
either create a single-threaded apartment or free-threaded apartment.
You can either manually set the ApartmentState for the thread that
creates the COM object (if it isn't the main thread) OR mark the Main
method w/ STAThreadAttribute (or MTAThreadAttribute if the COM object
is free threaded).
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top