|
|
|
date: Fri, 31 Aug 2007 09:54:41 -0400,
group: microsoft.public.platformsdk.com_ole
back
Threads in MTA creating STA objects strangeness.
i have multiple threads running in the MTA (multithreaded apartment). Each
thread constructs a single WinHttpRequest object, which is a single STA
(single threaded apartment) object. The thread then repeatedly fetches
content and when finished frees the object.
The strangeness is that even though i have three threads running, the actual
fetching of content over the network is being done serially. If a request
takes 1 second to complete, and i have 3 threads running, then the time to
fetch data of a particular request will be either 1, 2, or 3 seconds. If i
have 5 threads running, then the time for a thread to receive data will be
either 1, 2, 3, 4, or 5 seconds.
Now the smart ones among you will already know what's going wrong, and how
to fix it. And i did randomly try that, and it did work. What we're trying
desperatly to figure out is Why. Scouring MS documentation, what is
happening is not what is documented. But first we'll have to go through
everything else i tried...
At first i thought that the web server was limiting each client machine
(client ip) to one request. i know web servers typically limit the number of
simultaneous connection requests from one machine. But it's usually limited
to three connections, not one. Also, if i launch three applications, each
spawning one thread, then the response times for all three are 1 second. Web
servers don't know the process id of the client making the request, so that
can't be it. This makes no sense.
Next i thought maybe there was an option in WinHttpRequest itself - that it
limits it's number of simultaneous connections to 1. But i couldn't belive
that MS would do that, such a feature makes no sense. Plus, how would the do
it, how could they do it. And finally, why would it be that it only limits
you in one process to one request at a time, and another process can issue
another single request. That makes no sense.
So i'm back to wondering how three threads, each creating their own object,
can have access to that object serialized with two other threads. How can
the object created by Thread B possibly be not allowed to run while Thread A
is talking to it's own object? It makes no sense.
Then i remember some tid-bit of innane trivia i heard one: each process only
gets one MTA apartment, and all MTA threads share that apartment. MTA means
that the threads are free to stomp all over each other, which is not a
problem in my case since we don't do any stomping of any kind. STA means
COM will step in and protect everyone from talking to objects from the wrong
thread - but since i'm only ever talking to my WinHttpRequest object from
the thread that created it, i don't need to worry about that. But then we
realized that even though i'm talking to the WinHttpRequest object in a
thread safe manner, the thread is in an/the MTA apartment, while the
WinHttpRequest object claims to only support being in an STA apartment.
Okay, so this is interesting. The apartment the thread is incompatible with
the threading model required by the WinHttpRequest.
Now the question is what does COM do if my thread, inside the MTA apartment,
tries to create an object that requires to be used from an STA apartment?
Consulting the documentation:
http://msdn2.microsoft.com/en-us/library/ms809971.aspx
Understanding and Using COM Threading Models
Client: MTA
Server: STA
Server created in: A new STA, created by COM
Inter-object communication via: Marshalling
Interesting. COM will launch a new thread, put the thread in it's own STA
apartment, create the COM object, and give me a proxy stub interface. And
anytime i want to call a method on that object COM will:
- serialize the method parameters
- send a message to the STA thread which says to run the method (because
it's a SendMessage, my thread is suspended)
- executes the method from the context of the STA thread on the real
object
- marshall the results back
and my thread resumes, with execution complete. Okay, that's all well and
good. It explains how my thread can be suspended while a method call gets
transferred to another thread. But it doesn't explain how two independant
threads ThreadB and ThreadC can be suspended while ThreadA talks to it's
object.
Perhaps COM only ever created one WinHttpRequest object? Perhaps because all
MTA threads live in one apartment, and the COM object must be in an STA
apartment, COM will do an optimization and only ever create one object.
Since it promises to serialize all access to that single object, the object
has been made thread safe. No, that can't be. If three threads were to
talk to the same object, even though a single method call access is
protected, there's state information associated with the WinHttpRequest
object. The three threads would stomp all over each others settings. That
makes no sense.
So i try to reason this out:
1. i created three threads.
2. Each thread enters the MTA apartment
3. Each thread creates it's own WinHttpRequest object
4. Because the thread's apartment model is incompatible with objects
apartment model, COM spawns three additional threads, one for each COM
object.
5. Each thread calls a method on the COM object.
6. Each thread is suspended while a message is sent to each of the three
STA apartment threads that hold the three com objects.
7. Each STA thread executes the method, and returns the result.
8. Each thread get's it's result.
Each thread *should* takes about 1 second to return it's result, and yet
evidence shows that the access to the WinHttpRequst objects is being
serialized through one thing. What is the one thing through which all
access to these objects is being funneled?
A colleague proposed a theory: What if COM is only creating one STA thread
to handle all three of these inter-apartment COM objects? That would
explain it, COM must be creating only one thread to handle all these out of
apartment objects, rather than a separate thread for each object. But that
can't be it, the documentation says quite clearly:
http://msdn2.microsoft.com/en-us/library/ms809971.aspx
Understanding and Using COM Threading Models
Client: MTA
Server: STA
Server created in: A new STA, created by COM
Inter-object communication via: Marshalling
"A new STA, created by COM"
When the threading model of the server is incompatible with the threading
model of the server, the COM object is created in a *NEW* STA, not an
existing STA.
"A new STA, created by COM"
In reality, it's not being created in a new STA, it's being created in an
existing STA. This is a problem with COM, because it's leaking an
implementation detail rather than abstracting it. And yet, it makes no
sense that COM would only use a single thread to handle all these out of
apartment objects. The performance contention is just horrendous. That makes
no sense.
And yet, if i change the threads to enter their own STA apartment, none of
the delays happen. Each thread then receives a response in about 1 second,
rather than 1,2, or 3 seconds.
So what is actually going on that three MTA threads, each talking to their
own STA object have serial access to the three STA objects. And if i change
the threads to STA threads, each thread get's fast access? COM creating only
a single thread for all out of apartment objects would explain it - but
that's just a bad design idea, plus it violates the documentation.
This makes no sense.
date: Fri, 31 Aug 2007 09:54:41 -0400
author: Ian Boyd
Re: Threads in MTA creating STA objects strangeness.
The Service Control Manager (SCM) puts instances of classes marked as apartment-threaded that are created from an MTA or a TNA into
a process's Host STA. From Tim Ewald's book (page 124): "The Host STA is created and managed by the plumbing specifically to house
objects that require a high degree of thread affinity (ie. an STA environment), but that are created from the MTA or TNA."
Thus, all three instances of the WinHttpRequest object are in the same STA. This is clearly reflected in the behaviour that you are
describing. Because they are in the same STA, the methods and properties are being served by the same thread, and hence access is
being serialized.
>
> Interesting. COM will launch a new thread, put the thread in it's own STA apartment, create the COM object, and give me a proxy
> stub interface.
This is incorrect. The second instantiated object will join the same host STA as the first instantiated object.
>
> So i try to reason this out:
> 1. i created three threads.
> 2. Each thread enters the MTA apartment
> 3. Each thread creates it's own WinHttpRequest object
> 4. Because the thread's apartment model is incompatible with objects apartment model, COM spawns three additional threads, one
> for each COM object.
Wrong. All three threads belong to the same MTA. Any WinHttpRequest objects that are instantiated are created in the same STA.
> A colleague proposed a theory: What if COM is only creating one STA thread to handle all three of these inter-apartment COM
> objects? That would explain it, COM must be creating only one thread to handle all these out of apartment objects, rather than a
> separate thread for each object. But that can't be it, the documentation says quite clearly:
>
> http://msdn2.microsoft.com/en-us/library/ms809971.aspx
> Understanding and Using COM Threading Models
> Client: MTA
> Server: STA
> Server created in: A new STA, created by COM
> Inter-object communication via: Marshalling
>
> "A new STA, created by COM"
>
> When the threading model of the server is incompatible with the threading model of the server, the COM object is created in a
> *NEW* STA, not an existing STA.
I believe this is true if the COM server were an executable rather than in-proc DLL. However, for in-proc DLL's the STA actually
belongs to the host, and not the COM server. This is what Tim Ewald is describing when he talks about the "host STA".
The first object is indeed created in a new STA, called the host STA. However, the second object joins the first object in the same
STA.
>
> "A new STA, created by COM"
>
> In reality, it's not being created in a new STA, it's being created in an existing STA.
... as explained.
> This is a problem with COM, because it's leaking an implementation detail rather than abstracting it.
I have no clue what you mean by this.
> And yet, it makes no sense that COM would only use a single thread to handle all these out of apartment objects.
Well, it makes complete sense if you are the author of the STA COM object. It allows one to focus on the implementation details
without worring about thread-safety.
> The performance contention is just horrendous. That makes no sense.
Well, you have chosen to use the WinHttpRequest COM object, which was designed for VB apps. VB apps use STA's.
Frankly, why don't you write your own WinHttpRequest object and make it free-threaded? Or simply use the WinInet API directly?
HTH,
Brian
date: Fri, 31 Aug 2007 10:09:30 -0700
author: Brian Muth
|
|