🏢

非同期メイン関数でスレッドアパートメントがSTAにならない

2023/01/14に公開

次のコードのようにメイン関数にSTAThreadAttributeを付与することで、スレッドアパートメントをSTAに指定することができます。

[STAThread]
static void Main()
{
    Console.WriteLine(Thread.CurrentThread.GetApartmentState()); // STA
}

ところがメイン関数を非同期にするとSTAThreadAttributeを付与していたとしても、スレッドアパートメントがMTAになります。

[STAThread]
static async Task Main()
{
    Console.WriteLine(Thread.CurrentThread.GetApartmentState()); // MTA!
}

原因は単純で、非同期のメイン関数はコンパイルすると以下のように展開されるからです。
ユーザーが定義したメイン関数は適当な別名にリネームされ、本来のメイン関数から呼び出されるようになります(本来は非同期処理用のステートマシンが展開されもっとごちゃごちゃしてますが)。

[STAThread]
static async Task <Main>()
{
    Console.WriteLine(Thread.CurrentThread.GetApartmentState()); // MTA!
}

static void Main()
    => <Main>().GetAwaiter().GetResult();

なので、とりあえずの対策としては手動でスレッドアパートメントを変更しておけば大丈夫です。

static async Task Main()
{
    Thread.CurrentThread.SetApartmentState(ApartmentState.Unknown);
    Thread.CurrentThread.SetApartmentState(ApartmentState.STA);
    Console.WriteLine(Thread.CurrentThread.GetApartmentState()); // STA
}

runtimeリポジトリのIssueを覗いてみたところ、この現象に躓いてしまった人がちょくちょくいるみたいで、似たようなIssueがちらほらありました。とりあえずこれは仕様とのことで、WinFormsとかWPFみたいな、STAなUIスレッドが必須のアプリではそもそも非同期メイン関数使わないほうがいいよとのこと。

Discussion