【问题标题】:Set ApartmentState for async void main为 async void main 设置 ApartmentState
【发布时间】:2018-05-13 04:53:22
【问题描述】:

我有一个 Windows 窗体应用程序。

现在我想使用async 方法。

从 C# 7.1 开始,我可以使用 async Main 方法:
https://docs.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,因为根据定义,当前线程有already started
  • 是的,这没有意义。

标签: c# async-await sta mta


【解决方案1】:

STA 是您承诺单个线程将操作 Windows 消息泵(通常隐藏在对 Application.Run() 的调用之后)的一个。当线程未被占用时,您将在线程上运行消息泵。

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

我无法让这两个概念保持一致。如果您想要 STA,您希望保留该线程并泵送消息。所以在这里使用async main 没有意义。


在这种特殊情况下(可以从await 后跟Application.Run 受益的各种初始化步骤我会使用async Main 没有 STAThread 属性。然后我会显式创建一个 STA 线程,专门用于运行 Windows 消息循环。

没有规定程序中的第一个线程必须是/a STA 线程,我认为这提供了最干净的分离。如果您的async Main 没有其他有用的工作要做,您可能希望在Main 和消息循环运行线程之间共享一个TaskCompletionSource,然后在Application.Run() 返回时发出完成信号。

【讨论】:

  • 要启动 STA 线程,请使用以下代码:var thread = new Thread(() =&gt; DoSomething()); thread.SetApartmentState(ApartmentState.STA); thread.Start();
【解决方案2】:

STAThread 不适用于async main

这是known and open issue

问题在于将async main 转换为“正常”main 的代码不会复制该属性。

这里是more information,特别是关于未复制的属性。

some of the discussion 中,他们指的是使用SingleThreadedSynchronizationContext 以允许在STA 线程中惯用地使用异步代码。

【讨论】:

  • 谢谢,怀疑这是一个错误,但我自己找不到工作项。
  • @JürgenSteinblock - 我不确定这本身就是一个错误。正如我所指出的,您承诺使用 STA 运行消息泵。 Application.Run 的所有变体本质上都是阻塞的,直到消息泵被告知退出 - 所以要么您不调用 Application.Run()(或道德等价物),因此没有履行 STA 承诺,或者您正在无论如何都会阻止并且不会从async中受益。
  • @JürgenSteinblock - 或者,至少,如果你尝试用STAThread 标记async main 而不是复制属性,编译器应该会引发错误。
  • @Damien_The_Unbeliever 好吧,这个问题在dotnet/roslyn repo 中被标记为Bug,所以也许这会以某种方式解决。我同意,使用 async/await 和阻塞 Application.Run() 没有意义。但是我只将它用于在Run() 之前执行的代码,并且我不知道这会改变我的应用程序分隔状态。编译器或运行时错误比忽略属性要好。
【解决方案3】:

我知道这已经过时了,但 Damien 的回答和随后的评论帮助我解决了我的问题。我有一个控制台应用程序,我需要在其中调用 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。使用await 为应用程序方法创建了async main 方法(无[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;
}

【讨论】:

  • 这几乎让我到了那里,但在一切运行之后,我得到了一个 COM object that has been separated from its underlying RCW cannot be used 异常。
  • 我之前没有遇到过这个错误,但这可能会有所帮助(建议在每个线程中启动 COM 对象):stackoverflow.com/questions/1567017/…
  • 在调试模式下运行测试时发生在 MS 测试中。否则没有错误。
猜你喜欢
  • 2013-05-19
  • 2018-03-16
  • 2021-05-26
  • 2016-01-05
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-03-10
相关资源
最近更新 更多