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

 用心对待2502862725 发布于 2022-12-10 11:07

我们有一个程序可以作为服务或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 + 0×45!
08:U 000007fefdb3b949 RPCRT4 NdrpClientCall3 + 0x2e2!
09:U 000007fefde521d0 OLE32 ObjectStublessClient + 0x11d!
0A:U 000007fefdd0d8a2 OLE32 ObjectStubless +的0x42!
0B:ü000007fefdd2ea07 OLE32 CObjectContext :: InternalContextCallback + 0x31537
0℃ :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'+ 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 + 0x1dstring'+0x8c449
10:U 000007fef4e437a9 clr! ?? ::FNODOBFM::











Hans Passant.. 5

这是标准的终结器线程死锁.总是代码中的错误,很容易在服务或控制台模式应用程序中犯这样的错误.之所以发生这种情况,是因为您在代码中使用了单线程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服务器这篇文章.

1 个回答
  • 这是标准的终结器线程死锁.总是代码中的错误,很容易在服务或控制台模式应用程序中犯这样的错误.之所以发生这种情况,是因为您在代码中使用了单线程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服务器这篇文章.

    2022-12-11 02:11 回答
撰写答案
今天,你开发时遇到什么问题呢?
立即提问
热门标签
PHP1.CN | 中国最专业的PHP中文社区 | PNG素材下载 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有