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
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.