.NET Forum / .NET Framework / Interop / June 2005
COM/threading and difference between Service and Console App
|
|
Thread rating:  |
PJChan - 23 Jun 2005 02:40 GMT Hi, I've got an app that starts a thread to listen on a tcp port, then starts another thread to accept the socket connection and exchange some data with the client. This second thread creates a COM object for internal use. My problem is that if I test with a console app, the thread that creates the COM object seems to go to sleep and only wakes up when I stop the thread, however I don't have this problem if I run the app as a windows service! Is there a reason for this to happen? I suspect the issue has to do with threading model and the COM object (legacy library that I can't look into), but shouldn't I be seeing the same behaviour in both cases? What tool would you suggest I use to dig into this? Thanks
 Signature P.J. Chan
PJChan - 23 Jun 2005 08:43 GMT Adding a little more background: the console app creates thread A Thread A creates thread B and then waits on a tcp connection Thread B creates COM object and freezes
If the console app executes ThreadA.join(5000), the creation of the COM object succeeds and execution of thread B completes! But I don't get the same result if I simply do Thread.sleep(5000) from the console app.
Any idea what might be going on?
 Signature P.J. Chan
> Hi, I've got an app that starts a thread to listen on a tcp port, then starts > another thread to accept the socket connection and exchange some data with [quoted text clipped - 7 lines] > What tool would you suggest I use to dig into this? > Thanks PJChan - 23 Jun 2005 09:13 GMT Sorry, I keep missing bits of the story which might give someone a clue as to what's happening ...
After starting thread A, the console app waits on Console.Readline. Thread A can receive tcp msgs and start more Thread Bs as a result, all of them freezing when creating the same COM obj, and all remaining frozen no matter how many times thread A wakes up from tcp waiting.
But as soon as the console app calls ThreadA.join(5000) (I do it when Readline reads a letter "b"), all of the frozen B threads continue in parallel.
if instead of a console app I create thread A from a windows service (and thread A does the same as always: wait on tcp and create B for each msg received), thread B creates the COM obj with no problem. No need to do a join.
The only obvious difference here is that the console app is waiting for keyboard entry, while the service is doing whatever services do after being started. The service is responsive to Stop, which causes all threads to be stopped.
Anyhow, this is the full story this time. I hope someone can help rgds P.Chan
> Adding a little more background: > the console app creates thread A [quoted text clipped - 18 lines] > > What tool would you suggest I use to dig into this? > > Thanks Willy Denoyette [MVP] - 23 Jun 2005 21:43 GMT 1. What is the 'threadingmodel' key of the COM object in the registry? 2. What is the apartment type your threads. (MTA or STA), if you don't explicitly initialize them to join a STA their apartmentstate will be MTA. Now suppose the answer to 1 is (the most obvious) "apartment", that means the object must be instantiated/accessed on a STA thread. If the console application has the STAThread attribute set on Main, your object will live in the STA of the thread running Main() (called the main STA). That means also that calls from other threads must be marshaled, that means the STA thread must pump the message queue. Console.ReadLine() doesn't pump messages it blocks, that means that calls into your COM object will block the callers thread. Thread.Join() however, pumps the message queue (the CLR has limited pumping support for some other 'blocking' waits like Join). So, that explains the console app. behavior. Now back to your Service, suppose again the answer to 1 is 'apartment'. A service in general has no [STAThread] attribute on Main() (the default in VS), that means all threads will be MTA, unless you initialize them differently. Here the COM runtime spins up a STA thread (OLE runtime thread), called the "host STA" thread, creates the object and marshals the interface pointer back to the callers MTA thread and spins up a message pump. So again this explains why it doesn't block the callers thread in a service. However running COM objects on the host STA thread is bad, it's a performance killer and it doesn't scale (all calls from different threads must be marshaled and are automatically serialized).
One thing you should do to solve all these problems is to initialize your threads as STA before starting, this way the calls into COM are all direct calls (no marshaling/mesage pumping needed) Willy.
> Sorry, I keep missing bits of the story which might give someone a clue as > to [quoted text clipped - 56 lines] >> > What tool would you suggest I use to dig into this? >> > Thanks PJChan - 24 Jun 2005 03:57 GMT Willy, thankyou very much for your prompt response; it explained perfectly the behaviour I'm seeing. You are spot on on all the assumptions.
When you say that I should initialize threads to STA, you mean the ones that instantiate the object, right? (thread B in my text) Or do I have to do the same with thread A, which creates thread Bs upon receiving TCP msgs?
So I've changed thread B to start with ApartmentState set to STA (I verified that initial ApartmentState is unknown and then it accepts STA as its new value).
I still get the same results; is that what you would've expected given the way the Console apps run the main STA (in this case blocking it with a Readline)? Or was this supposed to fix the problem with the console app as well?
In the end I'm using the console app only for testing, as this is quicker than testing with the service, but my real target is the service, which seems to be working fine after the change. Is there anything I can do to verify for curiosity's sake that the COM object is not running on the host STA anymore?
Thanks again PJ Chan
 Signature P.J. Chan
> 1. What is the 'threadingmodel' key of the COM object in the registry? > 2. What is the apartment type your threads. (MTA or STA), if you don't [quoted text clipped - 86 lines] > >> > What tool would you suggest I use to dig into this? > >> > Thanks Willy Denoyette [MVP] - 24 Jun 2005 11:38 GMT You have to make sure there is no marshaling involved, that is you may not use the COM interface from another thread than from the creator thread.
This scenario, - Main thread creates and starts thread A, (Is your Main() attributed with STAThread ?) - A creates B, and initializes the thread to enter a STA before it starts B - In B (now a STA thread) creates the COM object and only code in B accesses it's interfaces. should not block on lack of pumping messages. If, however, you create an instance on B and use the interface from A, you have to pump messages to prevent blocking.
If your Main() has the STAThread attribute, there won't be a Host-STA thread in the process. If there is no such attribute on Main() and you create an 'apartment' object on the main thread or any other thread while there is not yet an STA thread in the process, the COM object will end on the Host-STA thread. There is a way to detect this by using a low level debugger, but if you understand the above simple rules, there is no need to go down that path.
Willy.
> Willy, thankyou very much for your prompt response; it explained perfectly > the behaviour I'm seeing. You are spot on on all the assumptions. [quoted text clipped - 133 lines] >> >> > What tool would you suggest I use to dig into this? >> >> > Thanks PJChan - 24 Jun 2005 13:43 GMT Willy, yes, the console app is decorated with STAThread as you first suspected (and the service app is not, as you also suggested). I confirm that the Main creates A, then A creates B and sets its ApartmentState to STA before starting it. B creates the COM obj, which is private (to be precisse, B creates instances of various classes until one of them creates the COM obj; all instances remain private within B). B does use some variables that belong to an object created within A (that object starts the thread B, and I use it to be able to pass parameters to the B thread), but doesn't share anything back to A or the Main. I still had the same problem, namely the console app gets blocked until I use Join, while the service continues to work. Can a console app be created without the STAThread decoration?
Thanks
 Signature P.J. Chan
> You have to make sure there is no marshaling involved, that is you may not > use the COM interface from another thread than from the creator thread. [quoted text clipped - 156 lines] > >> >> > What tool would you suggest I use to dig into this? > >> >> > Thanks PJChan - 24 Jun 2005 14:09 GMT Willy, I thought I should disclose a little more detail about the COM object in case this may be affecting the behaviour. The COM object is a VB6 wrapper around another COM DLL that is installed as an MTS component. That second COM dll only works in NT, and is the legacy dll that I ultimately need to preserve and the reason for me going through all this trouble. The wrapper allows me to develop and test in XP and other such versions of Windows, then deploy to the target NT machine. Manually I tell the VB6 obj whether it should instantiate the legacy COM (ie in production NT environment) or just pretend it does (in non-NT test environment). Here comes the interesting fact: the legacy COM has no Threading setting in the registry. My understanding is that this is typical of MTS components. Could this somehow be impacting the threading model/marshalling in my app?
Thanks once again
 Signature P.J. Chan
> Willy, yes, the console app is decorated with STAThread as you first > suspected (and the service app is not, as you also suggested). [quoted text clipped - 171 lines] > > >> >> > What tool would you suggest I use to dig into this? > > >> >> > Thanks Willy Denoyette [MVP] - 24 Jun 2005 19:35 GMT That's what I was afraid of (and guess I did ask about the threading model of your COM objects). Well, COM objects that have their "threadingmodel" empty are called single threaded objects, all these objects live in a single STA "the host-STA" served by a single thread. In your console scenario the Main() procedure runs on a STA thread which will serve as the Host-STA thread (the first thread in the process that initialized a STA) for all the Single Threaded COM objects.
Lets look how things relate now; Main thread -STA Thread A - MTA Thread B - STA creates --> VB6 STA COM object creates --> single threaded (ST) COM object the ST object will live in the Main STA threads apartment that means calls must be marshaled and the Main STA thread must pump messages (Console.ReadLine() doesn't pump , Thread.Join pumps).
This can be solved by, or - pumping the main STA thread, you can use System.Threading.CurrentThread.Join() instead of Console.ReadLine(), or - by initializing the main thread as MTAThread, in this case the B thread will become the Host STA and as it's the same thread as the COM objects thread, no marshaling is required.
If you carefully study above, you can easily understand why it works in a service right, and also why the change from MTA to STA for thread B didn't change anything, right?
Willy.
> Willy, I thought I should disclose a little more detail about the COM > object [quoted text clipped - 250 lines] >> > >> >> > What tool would you suggest I use to dig into this? >> > >> >> > Thanks PJChan - 27 Jun 2005 15:08 GMT Willy, yes everything has become clear; thanks. I guess my last question is whether there could be any side effects (other than things working the way I wanted in the first place) in turning the main of the console app from STA to MTAThread. Is there a particular reason for STA being the default?
Thanks for your help; this has been very interesting.
 Signature P.J. Chan
> That's what I was afraid of (and guess I did ask about the threading model > of your COM objects). [quoted text clipped - 264 lines] > >> > >> >> > internal > >> > >> >> > use. Willy Denoyette [MVP] - 28 Jun 2005 12:20 GMT For a VS C# console application the default is no attribute on Main(). For a windows application the default is [STAThread] and should remain like this.
Willy.
> Willy, yes everything has become clear; thanks. I guess my last question > is [quoted text clipped - 327 lines] >> >> > >> >> > internal >> >> > >> >> > use.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|