繁体   English   中英

程序可以作为服务或表单运行-主线程应该是MTA还是STA?

[英]Program can run as a service or a form - should main thread be MTA or STA?

我们有一个可以作为服务或Winforms应用程序运行的程序。 我们根据传入的命令行参数执行不同的行为。

如果我们以表单形式运行,我认为我们希望我们的入口点是STAThread。

CA2232:使用STAThread标记Windows窗体入口点

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

但是,如果我们作为服务运行,我们是否希望入口点是MTAThread? 人们通常如何处理?

我们发现了一些崩溃转储(作为服务运行时),似乎在其中存在终结器。

如果没有将主入口点标记为STAThread,则不会出现此问题。

线程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'+0x8c449
10:U 000007fef4e437a9 clr! ?? ::FNODOBFM::
string'+0x8c449
10:U 000007fef4e437a9 clr! ?? ::FNODOBFM::
字符串'+ 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

这是标准的终结器线程死锁。 始终是代码中的错误,很容易在服务或控制台模式应用中犯这样的错误。 发生这种情况是因为您在代码中使用了单线程COM对象。 非常常见的是,绝大多数COM类就像绝大多数.NET类一样,并且根本不是线程安全的。 由于COM的不同,它会自动满足线程安全要求。

是的,调用COM服务器方法的线程的单元类型是非常重要的细节。 当选择MTA时,则将其留给COM以保持对象线程安全。 当您选择STA时,您将保证您的线程行为良好,并且可以满足此类线程不安全对象的需要。 这样的承诺仅在GUI应用程序中易于实现。

实现该承诺的两个基本要求。 您一定不要阻塞线程,等待某种同步对象发出信号。 而且,您必须在.NET程序中添加一个消息循环Application.Run()。 需要消息循环才能从工作线程获取调用以在拥有COM对象的线程上运行,从而使其保持线程安全。 永不阻塞的要求是为了确保这样的调用能够取得进展,并且不会死锁,因为STA线程被阻塞并且没有泵送。

违反这些承诺会给您带来麻烦,代码将陷入僵局。 像终结器一样,它试图释放对象,以线程安全的方式调用IUnknown.Release()。 但是拥有COM对象的线程是紧张的。 因为它从未调用过Application.Run()或因为它被阻止,所以我们无法分辨出哪个。

您可以使线程加入MTA,但这并不总是可能的,并且会带来严重的性能后果。 当您这样做时,将强制COM运行时为对象提供一个安全的宿主,并将为其创建线程。 这很简单,但是有两个基本问题。 每次对COM对象的调用都会被封送,这可能非常昂贵。 在简单的属性getter调用上速度降低了x10000。 并且COM组件的作者必须提供帮助,他需要提供代理和存根实现。 将方法调用的参数从调用者线程复制到所有者线程并将结果复制回所需的额外代码。 得益于反射,在.NET中非常容易实现,而在COM中则不那么容易。 作者通常没有意识到这一需求,因此跳过了这一需求。 现在,您将被迫使用STA。

从这个问题尚不清楚您忽略了哪个要求。 调试器可以向您显示拥有COM服务器的另一个线程,很容易以这种方式识别死锁。 或者,只是忘了调用Application.Run(),您可以从代码中看到这一点。 这篇文章中 ,您可以找到使此类COM服务器安全的代码。

为什么不都做。 您不需要入口点是STA线程,只需要消息泵是STA线程即可。 您需要做的就是在以表单形式运行时启动一个非后台 STA线程,并让“ main thead”超出范围。

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());
    }
}

我只是将一个新线程作为非后台线程启动,然后让主线程终止

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();
        }
    }
}

STAThread属性在运行线程之前生效。 但是方法Main()在线程启动后被调用。

要解决此问题-运行其他线程:

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());
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM