While looking for a memoryleak in a vb.net WebService, I detected that finalizers where blocked, and so several objects where never released (e.g. System.Threading.ReaderWriterLock)
Google told me that this might be, because the STAThread Attribute is set on my main method.
It took a long while until I found out that VB.net uses STA-as default, while c# uses MTA.
When I added the MTAThread-Attribute to my Main Method, everything worked fine and objects are released.
So if I understand it right, the Finalizer-Thread is blocked in STA-Mode.
So far so good, but to be honest, I heard about STA and MTA today for the first time.
Can I switch between STA and MTA without any thoughts?
UPDATE
I'm still not sure if I can switch between MTA and STA without breaking my code.
Here are some more thoughts
I do not use COM Objects in my code.
But some other libraries I'm using seem to use them under the hood, for example OracleCommand
My application is written in vb.net, and so by chance it is set to STA-Appartment, since this is the vb.net default, which I did not know at development time
If I wrote my application in c#, it would be set to MTA by default
So do I need to care about the COM Objects that are used under the hood or not?
because the STAThread Attribute is set on my main method
Yes, that's a regrettable practice that VB.NET inherited from VB6. A strong goal in COM (the original underpinning of VB6 and what you use in your web service) was to hide the complexities of threading and dealing with thread-unsafe code automatically without the client programmer having to know anything about it. A COM object tells the COM runtime what kind of threading it supports. By far the most common choice is "Apartment", a confuzzling word that means that it is not thread-safe.
COM solves thread-safety issues by automatically marshaling a call of the COM method from a worker thread to the thread on which the COM object was created. Thus guaranteeing thread-safety for the COM object. The equivalent in .NET is Dispatcher.Invoke() or Control.Invoke(). Methods that you have to call explicitly in a .NET program to keep the thread-unsafe user interface working, it is done entirely automagically for a COM object.
That kind of marshaling is pretty expensive, it inevitably involves two thread context switches plus the overhead of serializing the method arguments, tens of thousands of CPU cycles at a minimum.
A thread can tell COM that it is a friendly home for a thread-unsafe COM object and will take care of the marshaling requirements, it marks itself as a Single Threaded Apartment. STA. Any calls it makes to a COM method do not have to be marshaled and run at full speed. If a call is made from a worker thread then the STA thread takes care of actually making the call.
An STA thread however has to abide by two very important rules. Breaking one of those rules causes very hard to diagnose runtime failure. Deadlock will occur if you break those rule, like you observed for your finalizer thread. They are:
An STA thread must pump a message loop. The equivalent of Application.Run() in a .NET program. It is the message loop that implements the universal solution to the producer-consumer problem. Required to be able to marshal a call from one thread to a specific other thread. If it doesn't pump then the call made on a worker thread cannot complete and will deadlock.
An STA thread is not allowed to block. Blocking greatly increases the odds for deadlock, a blocked thread isn't pumping messages. The lesser problem in a .NET program, the CLR has a great deal of support for pumping itself on calls like WaitHandle.WaitOne() and Thread.Join().
Sometimes the COM component itself will make hard assumptions about being owned by an STA thread. And use PostMessage() internally, usually to raise events. So even though you never actually make any calls on a worker thread, the component will still malfunction. WebBrowser is the most notorious example of that, its DocumentCompleted event won't fire when the thread doesn't pump.
Your web service no doubt violated the first bullet. You only get a message loop automatically in a Winforms or WPF application. And yes, poison to the finalizer thread since its final release call on the COM object must be marshaled to keep the object thread-safe. Deadlock is the inevitable outcome since the STA thread isn't pumping. A ratty problem that's pretty hard to diagnose, the only hint you get is that the program's memory usage explodes.
By marking the thread as MTA, you explicitly promise to not provide a safe home for an apartment-threaded COM server. COM is now forced to deal with the hard case, it must create a thread by itself to provide safety. That thread always pumps. While that can solve the problem with your web server, it should be noted that this is not a panacea. Those extra threads do not come for free and the calls are always marshaled so always slow. Getting too many of those helper threads is a ratty problem that's pretty hard to diagnose, the only hint you get is that the program's memory usage explodes :)
Automatic thread-safety is a very nice feature. It works 99% of the time without any hassles. Getting rid of the 1% failure mode is however a very major headache. Ultimately it boils down to the universal truth, threading is complicated and error prone. One approach is to not leave it up to COM but take the threading bull by the horns yourself. The code in this post could be helpful with that.
Related
I am learning multi threading concepts (in general and targeted to C#.NET). Reading different articles, still could not fully understand few basic concepts.
I post this question. "Hans Passant" explained it well but I was not able to understand some of its part. So I started googling.
I read this question which have no answers.
Is Multithreading and MTA same?
Suppose I write a WinForm application which is STA (as mentioned above its Main() method), still I can create multiple threads in my application. I can safely say my application is "multi-threaded". Does that also mean my application is MTA?
While talking about STA/MTA, most of the articles (like this) talk about COM/DCOM/Automation/ActiveX. Does that mean DotNet have nothing to do with STA/MTA?
No. MTA is a property of a single thread, just like STA. You now make the exact opposite promise, you declare that the thread does absolutely nothing to keep external code thread-safe. So no need to have a dispatcher and you can block as much and as long as you like.
This has consequences of course and they can be quite unpleasant. It is deadly if the UI thread of your program is in the MTA since it uses so many external components that are fundamentally thread-unsafe. The clipboard won't work, drag+drop doesn't work, OpenFileDialog typically just hangs your program, WebBrowser won't fire its events.
Some components check for this and raise an exception but this check isn't consistently implemented. WPF is notable, while apartment state normally matters only to unmanaged code, WPF borrowed the concept and raises "The calling thread must be STA, because many UI components require this." Which is a bit misleading, what it really means is that the thread must have a dispatcher to allow its controls to work. But otherwise consistent with the STA promise.
It can work when the component uses COM and the author has provided a proxy. The COM infrastructure now steps in to make the component thread-safe, it creates a new thread that is STA to give it a safe home. And every method call is automatically marshaled so it runs on that thread, thus providing thread-safety. The exact equivalent of Dispatcher.Invoke() but done entirely automatic. The consequence however is that this is slow, a simple property access that normally takes a few nanoseconds can now take multiple microseconds.
You'd be lucky if the component supports MTA as well as STA. This is not common, only somebody like Microsoft goes the extra thousand miles to keep their libraries thread-safe.
I should perhaps emphasize that the concepts of apartments is entirely missing in the .NET Framework. Other than the basics of stating the apartment type, necessary since .NET programs often need to interop with unmanaged code. So writing a Winforms app with worker threads is just fine, and those worker threads are always in the MTA, you do however get to deal with thread-safety yourself and nothing is automatic.
This is generally well-understood, just about everybody knows how to use the lock keyword, the Task and BackgroundWorker classes and knows that the Control.Begin/Invoke() method is required to update UI from a worker thread. With an InvalidOperationException to remind you when you get it wrong. Leaving it up to the programmer instead of the system taking care of thread-safety does make it harder to use threads. But gives you lots of opportunities to do it better than the system can. Which was necessary, this system-provided thread-safety got a serious black eye when Java punched it in the face during the middleware wars of the late 90s.
There are some questions but first let's start by this:
An Apartment is a context where a COM object is initialized and executed, and it can be a either single thread (STA), normally used for not thread-safe objects, or multi thread.
the term apartment, which describes the constructs in which COM
objects are created
From: https://msdn.microsoft.com/en-us/library/ms809971.aspx
So Multithreading and MTA are not the same, but MTA is Multithreaded.
We can say that STA and MTA are related to COM objects.
You can read more here: https://msdn.microsoft.com/en-us/library/ms693344(v=vs.85).aspx
So, for your second question, if your WinForm application is "multi-threaded" does not mean it is "MTA".
Finally, the MTA/STA concepts are older than .Net technology, but we cannot say that they have nothing related to, because .Net supports COM technology in both STA and MTA.
I expect my answer help you to undestand the difference between Apartment and Threading.
More interesting reading here:Could you explain STA and MTA?
I've recently encountered a STA-related error in my program when I tried to launch an OpenFileDialog in a WinForm. I've done some reading, and before I add the [STAThread] attribute to my main thread I want to know how it will affect my program's execution.
I am a foreigner to COM so not everything I read made sense to me. Some points that stuck with me are:
The [STAThread] attribute defines the application as using a single-threaded apartment model.
More specifically, it changes the state of the application thread to be single-threaded.
http://www.a2zdotnet.com/View.aspx?Id=93
The STA architecture can impose significant performance penalties when an object is accessed by many threads. Each thread's access to the object is serialized and so each thread must wait in line for its turn to have a go with the object.
http://www.codeproject.com/Articles/9190/Understanding-The-COM-Single-Threaded-Apartment-Pa
I understand the need for thread-safety but I still don't understand what STAThread does. In my program (which I inherited from another developer) the main thread launches several other threads, one of which initializes the UI forms - and I think this is where the problem arises. With [STAThread] added what happens to the new threads? Does this affect multi-thread communication for non-Windows objects?
The error occurs when I try to open an OpenFileDialog in one of my forms. I added the dialog to the form using the VS designer: it didn't work. I then attempted to create a dialog box in a global file which is run by the main thread and call that instance from my form. It had no effect.
[STAThread] or Thread.SetApartmentState() are a really, really big deal. You make a promise to the operating system that you write code that is well-behaved. It matters to lots and lots of code inside Windows as well as components you use that are not thread-safe. Standard examples of such code are the Clipboard, Drag + Drop, the shell dialogs (like OpenFileDialog), components like WebBrowser and many Windows sub-components that are wrapped by .NET classes.
Thread-safety is always a big deal, writing truly thread-safe code is very, very difficult. The .NET Framework itself accomplishes it very rarely. Very basic classes list List<> are not thread-safe.
By making the promise to behave well, you must abide by the rules of writing code in a thread that reports itself to be an STA thread. You must do two basic things:
You must pump a message loop. Aka Application.Run() in a Winforms or WPF app. A message loop is a basic mechanism by which you can get code to run on a specific thread. It is the universal solution to the producer-consumer problem. Which solves the thread-safety problem, if you call thread-unsafe code always from the same thread then it isn't unsafe anymore.
You must never block your thread. Blocking an STA thread is very likely to cause deadlock. Because it stops those chunks of code that are not thread-safe from being called. There is core support for this in the CLR, blocking an STA thread with WaitOne() causes it to pump a message loop itself.
These requirements are easily met in a Winforms or WPF app. They are class libraries that were completely designed to help you implement them. Almost every single aspect about the way they behave was affected by it.
You must mark the Main() method in a GUI app as [STAThread]. Rock-hard requirement when it creates windows.
Creating another thread that displays a window is supported and possible. This time you must call SetApartmentState() to switch to STA, it cannot be a thread-pool thread. Getting this right is very difficult, in Winforms you'll get bitten badly by the SystemEvents class if you use certain kind of controls. It has a knack to start raising its events on the wrong thread. Debugging such a problem requires black-belt skills that look like this. That's suppose to scare you.
I have an interesting problem that I haven't seen documented anywhere else (at least not this specific issue).
This issue is a combination of COM, VB6, and .NET and making them play nice.
Here's what I have:
A legacy VB6 ActiveX DLL (written by us)
A multi-threaded Windows service written in C# that processes requests from clients over the network and sends back results. It does this by creating a new STA thread to handle each request. Each request-handler thread instantiates a COM object (defined in the ActiveX DLL) to process the request and get the result (a string of XML is passed in, and it returns a string of XML back), explicitly releases the COM object, and exits. The service then sends the result back to the client.
All of the network code is handled using asynchronous networking (i.e. thread pool threads).
And yes, I know this is already a risky thing to be doing in the first place, since VB6 isn't very friendly with multi-threaded applications to begin with, but unfortunately it's what I am stuck with for the moment.
I've already fixed a number of things that were causing deadlocks in the code (for example, making sure the COM objects are actually created and called from a separate STA thread, making sure to explicitly release the COM objects before the thread exits to prevent deadlocks that were occurring between the garbage collector and the COM Interop code, etc.), but there is one deadlock scenario that I just can't seem to solve.
With some help from WinDbg, I was able to figure out what is happening, but I'm not sure how (or if) there is a way around this particular deadlock.
What's happening
If one request-handler thread is exiting, and another request-handler thread is starting at the same time, a deadlock can occur because of the way the VB6 runtime initialization and termination routines seem to work.
The deadlock occurs in the following scenario:
The new thread that is starting up is in the middle of creating a new instance of the (VB6) COM object to process an incoming request. At this point, the COM runtime is in the middle of a call to retrieve the object's class factory. The class factory implementation is in the VB6 runtime itself (MSVBVM60.dll). That is, its calling the VB6 runtime's DllGetClassObject function. This, in turn, calls an internal runtime function (MSVBVM60!CThreadPool::InitRuntime), which acquires a mutex and enters a critical section to do part of its work. At this point, it's about to call LoadLibrary to load oleaut32.dll into the process, while holding this mutex. So, now it's holding this internal VB6 runtime mutex and waiting for the OS loader lock.
The thread that is exiting is already running inside the loader lock, because it's done executing managed code and is executing inside the KERNEL32!ExitThread function. Specifically, it's in the middle of handling the DLL_THREAD_DETECH message for MSVBVM60.dll on that thread, which in turn calls a method to terminate the VB6 runtime on the thread (MSVBVM60!CThreadPool::TerminateRuntime). Now, this thread tries to acquire the same mutex that the other thread being initialized already has.
A classic deadlock. Thread A has L1 and wants L2, but Thread B has L2 and needs L1.
The problem (if you've followed me this far) is I don't have any control over what the VB6 runtime is doing in its internal thread initialization and teardown routines.
In theory, if I could force the VB6 runtime initialization code to run inside the OS loader lock, I would prevent the deadlock, because I am fairly certain the mutex the VB6 runtime is holding is specifically only used inside the initialization and termination routines.
Requirements
I can't make the COM calls from a single STA thread, because then the service won't be able to handle concurrent requests. I can't have a long-running request block other client requests either. This is why I create one STA thread per-request.
I need to create a new instance of the COM object on each thread, because I need to make sure each instance has its own copy of global variables in the VB6 code (VB6 gives each thread its own copy of all global variables).
Solutions I've tried that didn't work
Converted ActiveX DLL to ActiveX EXE
First, I tried the obvious solution and created an ActiveX EXE (out-of-process server) to handle the COM calls. Initially, I compiled it so that a new ActiveX EXE (process) was created for each incoming request, and I also tried it with the Thread Per Object compile option (one process instance is created, and it creates each object on a new thread within the ActiveX EXE).
This fixes the deadlock issue with respect to the VB6 runtime, because the VB6 runtime never gets loaded into the .NET code proper. However, this led to a different problem: if concurrent requests come into the service, the ActiveX EXE tends to fail randomly with RPC_E_SERVERFAULT errors. I assume this is because the COM marshalling and/or the VB6 runtime can't deal with concurrent object creation/destruction, or concurrent method calls, inside the ActiveX EXE.
Force the VB6 code to run inside the OS loader lock
Next, I switched back to using an ActiveX DLL for the COM class. To force the VB6 runtime to run its thread initialization code inside the OS loader lock, I created a native (Win32) C++ DLL, with code to handle DLL_THREAD_ATTACH in DllMain. The DLL_THREAD_ATTACH code calls CoInitialize and then instantiates a dummy VB6 class to force the VB6 runtime to be loaded and force the runtime initialization routine to run on the thread.
When the Windows service starts, I use LoadLibrary to load this C++ DLL into memory, so that any threads created by the service will execute that DLL's DLL_THREAD_ATTACH code.
The problem is that this code runs for every thread the service creates, including the .NET garbage collector thread and the thread-pool threads used by the async networking code, which doesn't end well (this just seems to cause the threads to never start properly, and I imagine initializing COM on the GC and thread-pool threads is in general just a very bad idea).
Addendum
I just realized why this is a bad idea (and probably part of the reason it didn't work): it isn't safe to call LoadLibrary when you are holding the loader lock. See Remarks section in this MSDN article: http://msdn.microsoft.com/en-us/library/ms682583%28VS.85%29.aspx, specifically:
Threads in DllMain hold the loader lock so no additional DLLs can be dynamically loaded or initialized.
Is there any way to workaround these issues?
So, my question is, is there any way to work around the original deadlock issue?
The only other thing I can think of is to create my own lock object and surround the code that instantiates the COM object in a .NET lock block, but then I have no way (that I know of) to put the same lock around the (operating system's) thread exit code.
Is there a more obvious solution to this issue, or am I plain out of luck here?
As long as all of your modules work in one process, you can hook Windows API by replacing some system calls with your wrappers. Then, you can wrap the calls in a single critical section to avoid deadlock.
There are several libraries and samples to achieve that, the technique is commonly known as detouring:
http://www.codeproject.com/Articles/30140/API-Hooking-with-MS-Detours
http://research.microsoft.com/en-us/projects/detours/
And of course the implementation of wrappers should be done in native code, preferably C++. .NET detours work too for high-level API functions such as MessageBox, but if you try to reimplement LoadLibrary API call in .NET then you may get a cyclic dependency issue because .NET runtime internally uses LoadLibrary function during execution and does this often.
So the solution looks like this to me: a separate .DLL module which is loaded at the very start of your application. The module fixes the deadlock problem by patching several VB and Windows API calls with your own wrappers. All wrappers do one thing: wrap the call in critical section and invoke the original API function to do the real job.
EDIT: in retrospect, I don't think this will work. The problem is that the deadlock can occur at any time that a Win32 thread exits, and since Win32 threads don't map 1:1 to .NET threads, we can't (within .NET) force Win32 threads to acquire the lock before exiting. In addition to the possibility of the .NET thread that is exiting being switched to a different OS thread, there are presumably OS threads not associated with any .NET thread (garbage collection and the like) which may start and exit at random.
The only other thing I can think of is to create my own lock object
and surround the code that instantiates the COM object in a .NET lock
block, but then I have no way (that I know of) to put the same lock
around the (operating system's) thread exit code.
That sounds like a promising approach. I gather from this that you
are able to modify the service's code, and you say each thread
explicitly releases the COM object before exiting, so presumably you
could claim a lock at this point, either just before explicitly
releasing the COM object or just after. The secret is to choose a
type of lock that is implicitly released once the thread holding it
has exited, such as a Win32 mutex.
It is likely that a Win32 mutex object does not become abandoned until
the thread has completed all DLL_THREAD_DETACH calls, although I don't
know whether this behaviour is documented. I'm not familiar with
locking in .NET but my guess is that they are unlikely to be suitable,
because even if the right kind of lock exists, it would be likely to
be considered abandoned as soon as the thread reaches the end of the
managed code section, i.e., before the calls to DLL_THREAD_DETACH.
If Win32 mutex objects don't do the trick (or if you very reasonably
prefer not to rely on undocumented behaviour) you might need to
implement the lock yourself. One way to do this would be to use
OpenThread to get a handle to the current thread and save this in your
lock object, along with an event or similar object. If the lock has
been claimed and you want to wait for it to be available, use
WaitForMultipleObjects to wait until either the thread handle or the
event is signaled. If the event is signaled this means the lock has
been explicitly released, if the thread handle is signaled it was
implicitly released by the thread exiting. Obviously implementing
this involves a lot of tricky details (for example: when a thread
explicitly releases the lock, you can't close the thread handle
because another thread might be waiting on it, so you'll have to close
it when the lock is next claimed instead) but it shouldn't be too
difficult to sort these out.
I don't see any reason why you couldn't load an extra instance of the ActiveX control in your startup code and just hang onto the reference. Presto, no more loader lock issues since the VB6 runtime never shuts down.
Since I'm still exploring my options, I wanted to still see if I could implement a solution in pure .NET code without using any native code, for the sake of simplicity. I'm not sure if this is a fool-proof solution yet, because I'm still trying to figure out whether it actually gives me the mutual exclusion I need, or if it just looks like it does.
Any thoughts or comments are welcome.
The relevant part of the code is below. Some notes:
The HandleRpcRequest method is called from a thread-pool thread when a new message is received from a remote client
This fires off a separate STA thread so that it can make the COM call safely
DbRequestProxy is a thin wrapper class around the real COM class I'm using
I used a ManualResetEvent (_safeForNewThread) to provide the mutual exclusion. The basic idea is that this event stays unsignaled (blocking other threads) if any one particular thread is about to exit (and hence potentially about to terminate the VB6 runtime). The event is only signaled again after the current thread completely terminates (after the Join call finishes). This way multiple request-handler threads can still execute concurrently unless an existing thread is exiting.
So far, I think this code is correct and guarantees that two threads can't deadlock in the VB6 runtime initialization/termination code anymore, while still allowing them to execute concurrently for most of their execution time, but I could be missing something here.
public class ClientHandler {
private static ManualResetEvent _safeForNewThread = new ManualResetEvent(true);
private void HandleRpcRequest(string request)
{
Thread rpcThread = new Thread(delegate()
{
DbRequestProxy dbRequest = null;
try
{
Thread.BeginThreadAffinity();
string response = null;
// Creates a COM object. The VB6 runtime initializes itself here.
// Other threads can be executing here at the same time without fear
// of a deadlock, because the VB6 runtime lock is re-entrant.
dbRequest = new DbRequestProxy();
// Call the COM object
response = dbRequest.ProcessDBRequest(request);
// Send response back to client
_messenger.Send(Messages.RpcResponse(response), true);
}
catch (Exception ex)
{
_messenger.Send(Messages.Error(ex.ToString()));
}
finally
{
if (dbRequest != null)
{
// Force release of COM objects and VB6 globals
// to prevent a different deadlock scenario with VB6
// and the .NET garbage collector/finalizer threads
dbRequest.Dispose();
}
// Other request threads cannot start right now, because
// we're exiting this thread, which will detach the VB6 runtime
// when the underlying native thread exits
_safeForNewThread.Reset();
Thread.EndThreadAffinity();
}
});
// Make sure we can start a new thread (i.e. another thread
// isn't in the middle of exiting...)
_safeForNewThread.WaitOne();
// Put the thread into an STA, start it up, and wait for
// it to end. If other requests come in, they'll get picked
// up by other thread-pool threads, so we won't usually be blocking anyone
// by doing this (although we are blocking a thread-pool thread, so
// hopefully we don't block for *too* long).
rpcThread.SetApartmentState(ApartmentState.STA);
rpcThread.Start();
rpcThread.Join();
// Since we've joined the thread, we know at this point
// that any DLL_THREAD_DETACH notifications have been handled
// and that the underlying native thread has completely terminated.
// Hence, other threads can safely be started.
_safeForNewThread.Set();
}
}
I had written a rather complex code using VB6,VC6 about 20 years ago and I need to port it to visual studio.net.
I simply took the functions as I had written them along with the header files corrected all the compile errors (which were MANY) and then tried to load it. got "loaderlock closed"
I then decided to redo all the files starting from those that few other files depended upon and then worked my way up and as I went I included only the header files that that particular file required. The result it loads now just fine. no more loaderlock closed.
the lesson for me is don't include any more header files in a particular cpp file than is absolutely necessary.
hope this helps
from a very happy camper!!
david
I found a decent looking example of how to call a delegate asynchronously with a timeout... http://www.eggheadcafe.com/tutorials/aspnet/847c94bf-4b8d-4a66-9ae5-5b61f049019f/basics-make-any-method-c.aspx. In summary it uses WaitOne with a timeout to determine if the call does not return before the timeout expires.
I also know that you should have an EndInvoke to match each BeginInvoke.
So what happens if the wait timeout expires? We (presumably) DON'T want to call EndInvoke as that will block. The code can go on to do 'other things', but have we leaked anything? Is there some poor thread someplace blocked waiting for a return that's never going to happen? Have we leaked some memory where the result-that-will-never-return was going to be placed?
I think this post talks about it very well:
From the post:
You can't terminate an executing async delegate if it's not your thread, but you can if it is. If you use the common BeginInvoke type methods, you get a thread pool thread managed by the framework. If you use the Thread() class you get you own thread to manage, start, suspend, etc. as you like.
Developing asychronously requires that one decide who will manage the threads. The many different methods that execute asynchronously are using the ThreadPool threads behind the scenes.
Since you can't/shouldn't terminate a thread pool thread then you must design you code to communicate with the thread so that it can exit. The MSDN examples for the BackgroundWorker component demonstrates this kind of communication.
Sometimes your code may have the thread blocking waiting for IO. Here you would normally use a multiple object wait instead, waiting for IO or for a ManualResetEvent.
So in short, youll need to find a way to manage the threads yourself if there is a possibility of timing out and you want the thread to end.
You need to call EndInvoke().
Here is a link talking about what happens with EndInvoke():
Is EndInvoke() optional, sort-of optional, or definitely not optional?
Here is a link to the article in the accepted answer.
We had all been talking about the 'fire and forget' technique with asynchronous delegate invocation in various public forums. Many DevelopMentor instructors had written articles and example code showing the technique, and we had all described it in class. And of course it was in Don's book by then too. So when Microsoft eventually remembered to let the outside world know that this technique is not in fact legal, it was rather astonishing.
An MSDN link on the asynchronous pattern.
You will leak the resources held by the thread. There will be various bits of .NET remoting plumbing objects like the AsyncResult. Several unmanaged handles associated with the thread. All peanuts compared to the one megabyte of virtual memory address space you'll leak, held by the thread stack.
You cannot abort the thread in any way, the leak is permanent. When you have to deal with badly behaving code like this, your only good resource is to run it in a separate process so you can get Windows to clean up the shrapnel when you shoot the process in the head with Process.Kill(). Even that is not guaranteed, these kind of freezes tend to be associated with misbehaving device drivers. Process.Kill won't terminate a device driver thread. Easy to see: trying to abort the process with Taskmgr.exe will leave it running with one Handle. You have some hope if that doesn't happen.
I'm running a multithreaded windows service that need to call a VB6 dll. There's no documentation about this VB6 dll and this legacy system supports a very critical business process.
At first time (1st thread), this dll performs well. As other threads need access, it start provide wrong results.
I read one guys saying:
"Just be careful of one thing if you are using VB6. Your threading
model is going to have to change to support apartments if you are
running a multithreaded service. VB only supports multiple
single-threaded apartments, but .NET runs fully free threaded
normally. The thread that calls into the VB6 DLL needs to be
compatible with the DLL."
Another guy from team gave me the idea to put this ddl in a separated application domain. But I'm not sure.
How can we work with VB6 dll called from a multithreaded c# windows service application?
When the threads come in, are you saving objects and reusing them later on new threads? If you can, create the objects fresh for every thread. We have a situation like this with a data layer dll we use. If you create a connection on one thread, it can't be used from another. If you create a new connection on each thread, it works fine.
If it's slow to create your objects, look at the ThreadPool class and the ThreadStatic attribute. Threadpools recycle the same set of threads over and over to do work, and ThreadStatic lets you create an object that exists for one thread only. eg
[ThreadStatic]
public static LegacyComObject myObject;
As a request comes in, turn it into a job and queue it in your thread pool. When the job starts, check if the static object is initialised;
void DoWork()
{
if (myObject == null)
{
// slow intialisation process
myObject = New ...
}
// now do the work against myObject
myObject.DoGreatStuff();
}
You say
I'm running a multithreaded windows
service that need to call a VB6 dll.
There's no documentation about this
VB6 dll and this legacy system
supports a very critical business
process.
and at the same time you say
At first time (1ยบ thread), this dll
performs well. As other threads need
access, it start provide wrong
results.
I'd make very certain that Management is aware of the failure you're seeing because the code supporting the critical business process is old and undocumented, and is being used in a way it was never intended to be used, and was never tested to be used. I bet it's also never been tested to be used from .NET before, has it?
Here's my suggestion, and this is similar to something I've actually implemented:
The VB6 DLL expects to be called on a single thread. Do not disappoint it! When your service starts, have it start up a thread of the appropriate type (I can't say, since I've deliberately forgotten all that STA/MTA stuff). Queue up requests to that thread for access to the VB6 DLL. Have all such access go through the single thread.
That way, as far as the VB6 DLL is concerned, it's running exactly as it was tested to run.
BTW, this is slightly different from what I've implemented. I had a web service, not a Windows Service. I had a C DLL, not VB6, and it wasn't COM. I just refactored all access to the thing into a single class, then put lock statements around each of the public methods.
This article on multithreading Visual Basic 6 DLL's provides some insight. It says:
To make an ActiveX DLL project
multithreaded, select the desired
threading options on the General tab
of the Project Properties dialog box.
This article says there are three possible models to choose from:
One thread of execution
Thread pool with round-robin thread assignment
Every externally created object is on its own thread
I assume that the default is one thread of execution, and that one of the other two options needs to be selected.
You might want to take a look at this: linky
And here is a snippet that caught my attention:
VB6 COM objects are STA objects, that means they must run on an STA thread.
You did create two instances of the object from two MTA threads, but the object itself will run on a single (COM (OLE) created) STA
thread, and access from the two MTA threads will be marshaled and synchronized.
So what you should do is, initialize the threads as STA so that each objects runs on his own STA thread without marshaling and you
will be fine.
Anyway, VB style COM objects are always STA. Now in order to prevent apartment marshaling and thread switching you need to create
instances in STA initialized apartments.
Note also that when you set the [MTAThread] attribute on Main, you effectively initialize the main thread as MTA, when you create
instances of STA objects from MTA threads COM will create a separate (unmanaged) thread and initialize it as STA (this is called the
default STA), all calls to STA objects from MTA threads will be marshaled (and incur thread switches), in some cases Idispatch calls
will fail due to IP marshaling failures.
So the advise is use STA (and therefore VB6) objects from compatible apartments only.