设置异步 void main 的 ApartmentState

11

我有一个Windows表单应用程序。

现在我想要使用异步方法。

自从C# 7.1以来,我可以使用async Main方法:
https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7-1

然而,现在我的STAThread属性被忽略了,我的应用程序在MTA中运行。这是出于设计考虑还是我可以强制我的应用程序再次在STA模式下运行?

/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
private static async Task Main(string[] args)
{
    // returns MTA
    Console.WriteLine("{0}", Thread.CurrentThread.ApartmentState);
}

@Evk - 您会收到一个 ThreadStateException 异常,因为根据定义,当前线程已经启动。请参考:https://msdn.microsoft.com/zh-cn/library/system.threading.thread.setapartmentstate(v=vs.110).aspx。 - Damien_The_Unbeliever
是的,当然,那没有意义。 - Evk
4个回答

10

STA是指承诺使用单个线程操作Windows消息泵的应用程序(通常隐藏在Application.Run()的调用后面)。当该线程没有其他任务时,您将在其上运行消息泵。

async方法是一种方法,当它没有更好的事情可做时,会释放其线程去做其他事情。

我无法让这两个概念相互匹配。如果您想要STA,则需要保留该线程并进行消息传递。因此,在那里使用async main是没有意义的。


在这种特定情况下(各种初始化步骤可能受益于await后跟Application.Run),我会使用async Main而不使用STAThread属性。然后,我将明确创建一个STA线程,专门用于运行Windows消息循环。
没有规定您的程序中的第一个线程必须是/ a STA线程,我认为这提供了最清晰的分离。如果您的async Main没有进一步有用的工作要做,则可能希望在Main和运行消息循环的线程之间共享TaskCompletionSource,然后在Application.Run()返回后发出完成信号。

5
要启动一个STA线程,请使用以下代码:var thread = new Thread(() => DoSomething()); thread.SetApartmentState(ApartmentState.STA); thread.Start(); - Maxence

5
STAThreadasync main 不兼容。
这是一个已知的并且未解决的问题,具体信息请参考此处
问题出在将 async main 转换成“普通”main时,代码并没有复制属性。 更多信息关于未被复制的属性。
讨论中,他们提到使用 SingleThreadedSynchronizationContext来允许在STA线程中习惯性地使用异步代码。

谢谢,我怀疑这是一个 bug,但是我自己找不到工作项。 - Jürgen Steinblock
1
@JürgenSteinblock - 我不确定这是否是一个错误。正如我所指出的,使用STA时,您承诺运行消息泵。所有Application.Run的变体都是阻塞的,直到消息泵被告知退出 - 因此,要么您不调用Application.Run()(或道德等效物),因此未实现STA承诺,要么您仍将阻塞并且无法从async中受益。 - Damien_The_Unbeliever
4
@JürgenSteinblock - 或者说,这个 bug 是编译器应该在你尝试使用 STAThread 标记一个 async main 时引发错误,而不是复制属性。 - Damien_The_Unbeliever
2
@Damien_The_Unbeliever 嗯,这个问题在 dotnet/roslyn 存储库中被标记为“Bug”,所以也许会以某种方式解决。我同意,在使用阻塞的 Application.Run() 时使用 async/await 没有意义。但是我只是在 Run() 之前执行代码时使用它,并且我不知道这会改变我的应用程序状态。编译器或运行时错误比仅忽略属性更好。 - Jürgen Steinblock

4

我知道这是旧的内容,但是达米恩的回答以及后续的评论对我的问题有所帮助。我有一个控制台应用程序,在其中需要调用async方法,这些方法在某些时候可能需要STA执行以使用OpenFileDialog

以下是我的代码示例,如果它对其他人有帮助(或者只是对我未来有帮助):

1. 创建扩展方法以作为STA运行线程

public static class Extensions
{
    public static void RunSTA(this Thread thread)
    {
        thread.SetApartmentState(ApartmentState.STA); // Configure for STA
        thread.Start(); // Start running STA thread for action
        thread.Join(); // Sync back to running thread
    }
}

2. 创建了 async main 方法,并在应用程序方法中使用await(没有 [STAThread] 属性)。

class Program
{
    static async Task Main(string[] args)
    {
        await App.Get().Run(args);
    }
}

3. 使用扩展方法将 OpenFileDialog 调用包装在 STA 中

public string[] GetFilesFromDialog(string filter, bool? restoreDirectory = true, bool? allowMultiSelect = true)
{
    var results = new string[] { };
    new Thread(() =>
    {
        using (var dialog = new OpenFileDialog())
        {
            dialog.Filter = filter;
            dialog.RestoreDirectory = restoreDirectory ?? true;
            dialog.Multiselect = allowMultiSelect ?? true;
            if (dialog.ShowDialog() != DialogResult.OK)
                return; // Nothing selected
            results = dialog.FileNames;
        }
    }).RunSTA();
    return results;
}

这个几乎让我成功了,但是在所有东西运行之后,我得到了一个“已与其基础 RCW 分离的 COM 对象无法使用”的异常。 - Mike Lowery
我以前没有遇到过那个错误,但这可能会有所帮助(建议在每个线程中初始化COM对象):https://dev59.com/DHI_5IYBdhLWcg3wAeLl - Jason W
在调试模式下运行测试时,MS Test 发生了这种情况。否则没有错误。 - Mike Lowery

0

想回复https://dev59.com/FVYN5IYBdhLWcg3wXnJa#47553573,但评论不允许换行,使代码格式看起来很糟糕。

所以基于他们的帖子和提供的有用链接,在那里显示了编译器正在做什么(https://github.com/dotnet/roslyn/issues/22112#issuecomment-329462480)。 因此,手动执行可能会起作用。

[STAThread]
private static void Main(String[] args)
{
    // normal startup stuff, like Application.EnableVisualStypes();
    MainAsync(args).GetAwaiter().GetResult();
}

private static async Task MainAsync(String[] args)
{
    // do stuff
    // can await if want in here
}

这样做行不通,因为MainAsync仍然是异步执行的。使用SetApartmentState创建一个线程可以解决问题。我将其封装在一个扩展方法中,这样我就可以使用await Task.Factory.StartNew(() => .., ApartmentState.STA)来调用它。 - Jürgen Steinblock

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