如何在C# 9中使用顶层Program.cs处理[STAThread]?

6

我刚开始接触C# 9,并尝试在普遍存在的Program.cs中实现顶层语句。我在一个案例中成功实现了这一点,但在第二个案例中,应用程序在OpenFileDialog()中出现了ThreadStateException异常。

我替换了生成的Program.cs文件。

using System;
using System.Windows.Forms;

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

使用

using System.Windows.Forms;
using MapLines;

Application.SetHighDpiMode(HighDpiMode.SystemAware);
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());

这是OpenFileDialog中的异常情况。

System.Threading.ThreadStateException
  HResult=0x80131520
  Message=Current thread must be set to single thread apartment (STA) mode before OLE calls can be made. Ensure that your Main function has STAThreadAttribute marked on it. This exception is only raised if a debugger is attached to the process.
  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 MapLines.MainForm.OnOpenImageClick(Object sender, EventArgs e) in C:\Users\evans\Documents\Visual Studio 2019\Projects\Map Lines\Map Lines\MainForm.cs:line 664
   at System.Windows.Forms.ToolStripItem.RaiseEvent(Object key, EventArgs e)
   at System.Windows.Forms.ToolStripMenuItem.OnClick(EventArgs e)
   at System.Windows.Forms.ToolStripItem.HandleClick(EventArgs e)
   at System.Windows.Forms.ToolStripItem.HandleMouseUp(MouseEventArgs e)
   at System.Windows.Forms.ToolStrip.OnMouseUp(MouseEventArgs mea)
   at System.Windows.Forms.ToolStripDropDown.OnMouseUp(MouseEventArgs mea)
   at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
   at System.Windows.Forms.Control.WndProc(Message& m)
   at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
   at System.Windows.Forms.ToolStrip.WndProc(Message& m)
   at System.Windows.Forms.ToolStripDropDown.WndProc(Message& m)
   at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
   at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, WM msg, IntPtr wparam, IntPtr lparam)

回到原始的Program.cs,其中有[STAThread]修复了它。
我找到的关于C# 9新特性的文章没有提到这一点。这似乎很重要,因为可能有很多应用程序使用OpenFileDialog()和其他功能。据我所知,Winforms需要单线程公寓(STA)处理。在需要STA线程时是否有使用顶级语句的方法?

4
不,需要传统风格。 - Hans Passant
2个回答

11

这是一个可以正常运行的最简示例:

using System.Threading;
using System.Windows.Forms;

// It is unfortunate but we have to set it to Unknown first.
Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown);
Thread.CurrentThread.SetApartmentState(ApartmentState.STA);

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(defaultValue: false);
Application.SetHighDpiMode(HighDpiMode.PerMonitorV2);

Application.Run(new Form());

来源:dotnet/winforms Github 的此问题单:https://github.com/dotnet/winforms/issues/5071


5
using System.Threading;
using System.Windows.Forms;
using MapLines;

var thread = new Thread(() =>
{
    Application.SetHighDpiMode(HighDpiMode.SystemAware);
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);
    Application.Run(new MainForm());
});
thread.SetApartmentState(ApartmentState.STA);
thread.Start();

1
不确定为什么这个回答现在有一个负投票。它看起来合理并且似乎有效。让我试一试一段时间看看是否有什么问题(或者是否有更好的方法),然后我会将其标记为解决方案。使用这个解决方案还是从模板生成的原始版本更好可能是值得讨论的,但它回答了我的问题。 - Kenneth Evans
我思考了很长时间,目前还没有找到更好的解决方法(也许在C# 9.1+中会出现解决这个问题的东西)。另一个可能的改进是NuGet包,它将简化这段代码,只需调用Main.Run(() => {your-code})即可。 - Konstantin S.
1
我一直在使用这种方法,它似乎很有效。从实际角度来看,我没有看到什么不同之处。我点了赞。 - Logan K
这个可以工作...从“哇,看看C# 9多么棒,不需要所有那些样板代码就能开始!”的角度来看,这有点让人失望,尽管维护者们很棒。对于不需要STAThread的任何东西都很好,但否则这并不是太好。 - Jay Croghan
thread.Start() 后面使用 thread.Join() 来等待线程执行完毕。 - unknown6656
thread.Join()会阻塞主线程,只有在thread已经启动后才能退出。C#进程通常不会退出,直到所有非后台线程都停止。因此,我猜thread.Start()才是正确的方法。 - mycroes

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