STAThread丢失,但它存在。

6
这是我在程序中尝试打开OpenFileDialog时收到的错误消息:
“在进行OLE调用之前,当前线程必须设置为单线程公寓(STA)模式。请确保您的Main函数已标记STAThreadAttribute。只有在调试器连接到进程时才会引发此异常。”
这个错误信息的问题在于我的Main方法确实已经附加了STAThread属性。我不知道如何处理这个问题。如果已经有了一个属性,怎么添加另一个呢?重复添加不是一个好选择,而我尝试删除它、构建应用程序、再添加并重新构建,但都没有成功。我真的不明白。
private void btnOldFind_Click(object sender, EventArgs e)
{
     openFileDialog1.Multiselect = false;
     openFileDialog1.FileName = "";
     openFileDialog1.ShowHelp = false;
     openFileDialog1.AutoUpgradeEnabled = true;
     openFileDialog1.InitialDirectory = @"C:\";
     openFileDialog1.Filter = "Microsoft Installer (*.msi)|*.msi|All Files (*.*)|*.* ";
     openFileDialog1.FilterIndex = 1;
     openFileDialog1.RestoreDirectory = true;

     if (openFileDialog1.ShowDialog() == DialogResult.OK)
     {
         textBoxOldInstallation.Text = openFileDialog1.FileName;
     }
}

主方法是:

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new Form1());
    }
}

并没有显式地进行线程处理。说实话,这只是一个相当基本的程序。

编辑2:

下面是完整的错误消息,包括调用堆栈:

System.Threading.ThreadStateException未处理 Message="当前线程必须设置为单线程公寓(STA)模式,才能进行OLE调用。请确保Main函数上标记了STAThreadAttribute。如果调试器连接到进程,则会引发此异常。" Source="System.Windows.Forms" StackTrace: at System.Windows.Forms.FileDialog.RunDialog(IntPtr hWndOwner) at System.Windows.Forms.CommonDialog.ShowDialog(IWin32Window owner) at System.Windows.Forms.CommonDialog.ShowDialog() at MSI_Comparison_GUI.Form1.btnOldFind_Click(Object sender, EventArgs e) in c:\tfs\DocuWare .NET\DocuWare NewGen\src\Tools\MSI_Comparison\MSI_Comparison_GUI\Form1.cs:line 70 at System.Windows.Forms.Control.OnClick(EventArgs e) at System.Windows.Forms.Button.OnClick(EventArgs e) at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent) at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks) at System.Windows.Forms.Control.WndProc(Message& m) at System.Windows.Forms.ButtonBase.WndProc(Message& m) at System.Windows.Forms.Button.WndProc(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m) at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m) at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam) at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg) at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData) at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context) at System.Windows.Forms.Application.Run(Form mainForm) at MSI_Comparison_GUI.Program.Main() in c:\tfs\DocuWare .NET\DocuWare NewGen\src\Tools\MSI_Comparison\MSI_Comparison_GUI\Program.cs:line 18 at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args) at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args) at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly() at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart() InnerException:


已添加以供您阅读愉悦 :P - Christopher B. Adkins
你确定你正在主线程中执行,并且openFileDialog1已经在主线程中创建了吗? - Daniel Rose
1
也许你的项目有多个Main方法,而你认为被使用的可能不是那个?这里使用的是MSI_Comparison_GUI.Program.Main(),位于c:\tfs\DocuWare .NET\DocuWare NewGen\src\Tools\MSI_Comparison\MSI_Comparison_GUI\Program.cs的第18行。 - Dirk Vollmar
1
@Adkins 如果是这样的话,你最后的选择就是尝试在另一个解决方案中使用尽可能少的代码来重现此问题。如果可以的话,请打开 Connect w/MS。将地址发布在此处,我会为其投票支持。如果无法重现,则必须逐行、逐个部分类、逐个项目文件地查看两个项目之间的区别。很抱歉我不能提供更多帮助。 - user1228
对于在2021年到达此处的其他人的注意事项,但可能不是OP的问题:如果您的入口点是异步的(或者至少是异步返回任务),则STAThread属性也将被忽略。在这种情况下,即使尝试使用SetApartmentState显式设置线程模型,也会失败。看起来,如果您的主入口点是异步的,则必须是MTA。 - Yort
显示剩余4条评论
5个回答

15

你可能遇到了在Connect上报告的以下问题1:

.vshost.exe在调试.exe文件时会强制使用错误的线程模型,如果同一目录下存在同名的.dll文件

根据该问题,当您的输出文件夹中同时存在myprogram.exemyprogram.dll文件时,Visual Studio的托管进程即myprogram.vshost.exe会强制使用错误的单元状态。

这个问题可能特定于某些较旧版本的Visual Studio(2005),而我无法使用VS 2010重现它。

显然的解决方法是更改dll的名称或将其移动到其他文件夹中。

可能出现这种情况是因为您将项目的输出类型从类库更改为Windows应用程序。

1尚不清楚是否由Microsoft确认了此问题,只是说这个问题超出了VS产品团队的责任范围。


该死,我正在寻找这样的东西。我的功夫失败了。 - user1228
@Hans Passant:由于我无法重现这个问题,很难知道原因。个人认为可能是vshost机制中的一个错误,但也有可能与微软的shell团队有关,或者与某些懒惰的支持工程师有关;-) - Dirk Vollmar
1
@Hans Passant:事实上,这就是罪魁祸首。我没有外部的dll、模块、工具包等。我将程序剥离到只有纯自动生成的VS代码,再加上我的调用打开对话框的代码,问题仍然存在。一旦我改变了exe的名称(只是在后面添加了“_GUI”),问题就消失了。我仍然感到困惑,但至少问题已经解决并消失了! - Christopher B. Adkins
@0xA3 非常感谢您的回答。在 VS2010 SP1 上已确认。 - RHaguiuda
今天在VS2013中复制了一份项目。我重新组织了解决方案并重命名了一些项目,但其中两个具有相同的程序集名称,而一个是库项目,因此我确实有一个具有相同名称的dll和exe。更改DLL程序集名称解决了这个问题。 - Kilazur
显示剩余2条评论

6
您遇到了一个看似无解的堆栈跟踪问题。很明显,线程不是问题的原因,所有内容都在主线程上运行,并且您Main方法上的[STAThread]属性设置了公寓状态。堆栈跟踪表明确实是入口点。
坏消息是,某种附加组件正在与您的主线程交互。它可能会做一些讨厌的事情,例如调用CoUninitialize太多次。我曾经遇到过这种情况,花了我一个月的时间才找到它。通过项目+属性、调试选项卡,在“启用非托管代码调试”中进行诊断。这可以让您查看加载到程序中的DLL,显示在输出窗口中。
第一个线索是对话框第一次显示正常,但第二次失败。然后,您的程序中可能有某种外壳扩展处理程序。使用SysInternals的AutoRuns实用程序并禁用任何不是由Microsoft制作的外壳扩展处理程序。
当对话框立即失败时,情况就变得更加困难。然后使用调试+窗口+模块并浏览DLL列表。注意它们来自哪里,显示在路径列中。不要相信任何不像.NET或Microsoft DLL的东西。特别是没有符号文件时,你启用了Microsoft符号服务器,这是一个线索。解决这个问题的好方法是将该列表与另一台没有此问题的计算机上看到的列表进行比较。
我有一个关于这个的战斗记录。我的COM代码在数百台机器上崩溃,我只有一个minidump。花了我一个月的时间才发现源头,一个名为ffdshow的开源项目。它被广泛分布,也有不同的名称。它有一个错误,调用CoUnitialize太多次。这个错误存在于两年的版本中,但大约一年半前得到了修复。非常难以诊断,直到我开始查看旧版本才接近它。如果您在模块窗口中看到ffdshow,则说明您已经接近目标:)
祝你好运,让我们知道罪犯是谁。

你得到了一个不可能的堆栈跟踪。你说的"不可能"是什么意思?在我看来,这个堆栈跟踪看起来像是一个普通的调试堆栈跟踪…… - Dirk Vollmar
@0xA3:不可能,就像“那应该永远不会抱怨公寓状态”一样。 - Hans Passant

3

没有代码很难说。如果是控制台应用程序,请在调用方法之前添加以下内容:

Console.Write(System.Threading.Thread.CurrentThread.ApartmentState);

否则,
MessageBox.Show(System.Threading.Thread.CurrentThread.ApartmentState);

查看线程公寓状态的真实情况。


这是一个基本的Windows表单应用程序,故意没有使用线程。我按照建议使用了那个消息框,并告诉我状态为MTA。我真的不明白这怎么可能,因为我已经像Visual Studio自动完成的那样将Main标记为[STAThread],并且除了对OpenFileDialog的基本调用之外,我没有进行任何其他线程操作。 - Christopher B. Adkins
@adkins你确定没有使用掩盖多线程的框架类吗?比如BackgroundWorker或者在委托上使用"BeginInvoke"?多线程不会只是意外发生的。您能调试并让该异常再次抛出吗?如果您更新问题并提供异常对象中的调用堆栈,我可能可以告诉您何时发生了到MTA的转换,如果您正在使用这些框架类之一进行转换。 - user1228
它已经添加了,但不太美观。在此错误出现时,我根本没有使用任何东西。我只是加载程序并单击一个按钮打开OpenFileDialog。 - Christopher B. Adkins
@Adkins 有内部异常吗? - user1228
很遗憾,你看到的就是全部信息,没有任何内部异常。 - Christopher B. Adkins

0

尝试在主过程、创建对话框的点以及使用对话框的点设置断点。然后查看您实际所在的线程。此外,请检查这些点上Thread.CurrentThread.GetApartmentState()的值。


当前线程的公寓状态为MTA,但我不确定原因。 - Christopher B. Adkins
@ Adkins:所有这些位置,即使是在主方法中直接使用,它是否是MTA? - Dirk Vollmar
@0xA3 我把检查放在主方法的第一条语句,它是MTA。这真的是STAThread属性、主签名,然后是检查。 - Christopher B. Adkins
1
当您从资源管理器或命令行启动调试版本时,是否会得到相同的结果?请直接将 MessageBox.Show(System.Threading.Thread.CurrentThread.ApartmentState); 放置在您的主方法中。 - Dirk Vollmar
当我在 Visual Studio 之外运行它时,@0xA3 这个调试版本依然是 STA,并且工作正常。你是否能够帮我解答一下这个问题呢?我对你的回答表示感谢,但如果有可能的话,我希望能更好地理解一下。如果你能将这个答案写成回答的形式,我就可以标记为已回答。谢谢! - Christopher B. Adkins

0
t.SetApartmentState(ApartmentState.STA);

上面这行代码对我有用。你也试试看。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接