2

We have a program that can run as a service or as a winforms app. We do different behaviour based on command line parameters passed in.

If we are running as a form, I think we want our entry point to be STAThread.

CA2232: Mark Windows Forms entry points with STAThread

http://msdn.microsoft.com/query/dev12.query?appId=Dev12IDEF1&l=EN-US&k=k%28CA2232%29;k%28TargetFrameworkMoniker-.NETFramework

But if we are running as a service do we want our entry point to be an MTAThread? How do people normally deal with this?

We have found some crash dumps (while running as a service ) where we seem to get a stuck finalizer.

We don't get this problem if out main entry point is not marked as an STAThread.

Thread 2:
IP
00:U 00000000779312fa ntdll!NtWaitForSingleObject+0xa
01:U 000007fefd6d10dc KERNELBASE!WaitForSingleObjectEx+0x79
02:U 000007fefdd1e68e ole32!GetToSTA+0x8a
03:U 000007fefde53700 ole32!CRpcChannelBuffer::SwitchAptAndDispatchCall+0x13b
04:U 000007fefde5265b ole32!CRpcChannelBuffer::SendReceive2+0x11b
05:U 000007fefdd0daaa ole32!CAptRpcChnl::SendReceive+0x52
06:U 000007fefdd1cbe6 ole32!CCtxComChnl::SendReceive+0x15c
07:U 000007fefde5205d ole32!NdrExtpProxySendReceive+0x45
08:U 000007fefdb3b949 rpcrt4!NdrpClientCall3+0x2e2
09:U 000007fefde521d0 ole32!ObjectStublessClient+0x11d
0a:U 000007fefdd0d8a2 ole32!ObjectStubless+0x42
0b:U 000007fefdd2ea07 ole32!CObjectContext::InternalContextCallback+0x31537
0c:U 000007fefdd349d1 ole32!CObjectContext::ContextCallback+0x81
0d:U 000007fef4e439b6 clr!CtxEntry::EnterContext+0x232
0e:U 000007fef4e4383c clr!RCW::EnterContext+0x3d
0f:U 000007fef4e437e6 clr! ?? ::FNODOBFM::string'+0x8c449
10:U 000007fef4e437a9 clr! ?? ::FNODOBFM::
string'+0x8b99d
11:U 000007fef4ed326e clr!SyncBlockCache::CleanupSyncBlocks+0xc2
12:U 000007fef4ed319f clr!Thread::DoExtraWorkForFinalizer+0xdc
13:U 000007fef4dfab47 clr!WKS::GCHeap::FinalizerThreadWorker+0x109
14:U 000007fef4d4458c clr!Frame::Pop+0x50
15:U 000007fef4d4451a clr!COMCustomAttribute::PopSecurityContextFrame+0x192
16:U 000007fef4d44491 clr!COMCustomAttribute::PopSecurityContextFrame+0xbd
17:U 000007fef4e21bfe clr!ManagedThreadBase_NoADTransition+0x3f
18:U 000007fef4e21d90 clr!WKS::GCHeap::FinalizerThreadStart+0xb4
19:U 000007fef4da33de clr!Thread::intermediateThreadProc+0x7d
1a:U 00000000777d59ed kernel32!BaseThreadInitThunk+0xd
1b:U 000000007790c541 ntdll!RtlUserThreadStart+0x1d

Yurii
  • 4,811
  • 7
  • 32
  • 41
Derek
  • 7,615
  • 5
  • 33
  • 58
  • 1
    [Related](http://stackoverflow.com/q/127188/1997232). Why not to have 2 separate applications: MTA service and STA winform/wpf UI? It can be single exe (second application is inside resources, extract and run). – Sinatr Nov 21 '14 at 14:31
  • Running as MTA really only is a Band-Aid for your problem. If possible you should look at what COM component you are using and see if you can fix it to not block. – Scott Chamberlain Nov 21 '14 at 14:40

4 Answers4

5

This is a standard finalizer thread deadlock. Always a bug in your code, pretty easy to make such a mistake in a service or console mode app. It occurred because you use a single-threaded COM object in your code. Very common, the vast majority of COM classes are like the vast majority of .NET classes and not thread-safe at all. With a difference in COM, it takes care of the thread-safety requirement automatically.

And yes, the apartment type of the thread on which you call the methods of the COM server is a very important detail. When you select MTA then you leave it up to COM to keep the object thread-safe. When you select STA then you make a promise that your thread is well-behaved and accommodates the need of such a thread-unsafe object. Such a promise is only easy to implement in a GUI app.

Two basic requirements to fulfill that promise. You must never block the thread, waiting for some kind of synchronization object to get signaled. And you must pump a message loop, Application.Run() in a .NET program. The message loop is required to get a call from a worker thread to run on the thread that owns the COM object, thus keeping it thread-safe. The never-block requirement is there to ensure that such a call can make progress and not deadlock because the STA thread is blocked and not pumping.

Breaking those promises get you in trouble, code will deadlock. Like the finalizer did, it is trying to release the object, making the call to IUnknown.Release() in a thread-safe way. But the thread that owns the COM object is catatonic. Either because it never called Application.Run() or because it is blocked, we can't tell which.

You could make the thread join the MTA, but that isn't always possible and it has pretty grave perf consequences. When you do, the COM runtime is forced to give the object a safe home and will create a thread for it. That's dead easy, but has two basic problems. Every single call to the COM object will get marshaled, that can be very expensive. As much as x10000 slower on a simple property getter call. And the author of the COM component has to help, he needs to provide a proxy and stub implementation. Extra code that is required to copy the arguments of the method call from the caller thread to the owner thread and copy the result back. Very easy to do in .NET, thanks to Reflection, not quite that easy to do in COM. An author often doesn't realize the need and skips that requirement. You'll now be forced to use STA.

It isn't clear from the question which requirement you overlooked. The debugger can show you the other thread that owns the COM server, easy enough to recognize deadlock that way. Or just forgetting to call Application.Run() of course, you see that from your code. You can find code that makes a safe home for such a COM server in this post.

Community
  • 1
  • 1
Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
2

Why not do both. You don't need the entry point to be a STA Thread, you just need the message pump to be a STA thread. All you need to do is start up a non background STA thread when you are running as a form and let the "main thead" go out of scope.

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [MTAThread]
    static void Main()
    {
        var args = Environment.GetCommandLineArgs();
        if (args.Contains("--service", StringComparer.OrdinalIgnoreCase))
        {
            ServiceBase[] ServicesToRun;
            ServicesToRun = new ServiceBase[]
            {
                new Service1()
            };
            ServiceBase.Run(ServicesToRun);
        }
        else
        {
            Thread formsThread = new Thread(FormsMain);
            formsThread.IsBackground = false;
            formsThread.SetApartmentState(ApartmentState.STA);
            formsThread.Name = "New Main";
            formsThread.Start();

            //The program will continue on here and exit Main but the program 
            // will not shutdown because formsThread is not a background thread.
        }
    }


    static void FormsMain()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}
Scott Chamberlain
  • 124,994
  • 33
  • 282
  • 431
1

The STAThread atribute has effect before running thread. But method Main() is called after thread start.

To deal with this issue - run other thread:

static class Program
{
    static void Main()
    {
        if (RunningAsWindowsServiceCondition)
        {
            // run aaplication in windows service mode
        }
        else
        {
            // run as WinForms application
            var thread = new Thread(RunAsWinForms);
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();
        }
    }

    private static void RunAsWinForms()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}
szKarlen
  • 66
  • 4
0

I would simply start a new thread as a non-background-thread and let the mainthread terminiate

using System;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Boolean isService = false;                       // Figure it out by args, ...

            Thread t = new Thread(new ParameterizedThreadStart(HelperMain));

            t.IsBackground = false;
            t.Priority = ThreadPriority.Normal;

            if (isService)
            {
                t.SetApartmentState(ApartmentState.MTA);
            }
            else
            {
                t.SetApartmentState(ApartmentState.STA);
            }

            t.Start(args);
        }

        static void HelperMain(Object o)
        {
            Console.WriteLine(Thread.CurrentThread.GetApartmentState());
            Console.ReadLine();
        }
    }
}
Benj
  • 889
  • 1
  • 14
  • 31