Set ApartmentState for async void main

4.9k Views Asked by At

I have a Windows Forms app.

Now I want to use an async method.

Since C# 7.1 I can use an async Main method:
https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/csharp-7-1

However, now my STAThread attribute is ignored and my app runs in MTA. Is this by design or can I force my app to run in STA mode again?

/// <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);
}
4

There are 4 best solutions below

3
On BEST ANSWER

I know this is old, but Damien's answer and the subsequent comment helped me with my issue. I have a console app in which I need to call async methods that at some point may need STA execution to use the OpenFileDialog.

Here is my resulting code in case it helps others (or just my future self).

1. Created Extension Method for running thread as 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. Created async main method with await to application method (no [STAThread] attribute).

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

3. Use extension method to wrap OpenFileDialog call with 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;
}
1
On

An STA is one in which you promise that a single thread will operate a windows message pump (usually hidden behind a call to Application.Run()). You'll run the message pump on your thread when it's not otherwise occupied.

An async method is one which, when it's got nothing better to do, will release its thread to go off and do other things.

I can't get those two concepts to align. If you want STA, you want to keep hold of that thread and pump messages. So using async main doesn't make sense there.


In this particular circumstance (various initialization steps that could benefit from await followed by an Application.Run I would use the async Main without the STAThread attribute. I would then explicitly create an STA thread specifically for then running the windows message loop.

There's no rule that the first thread in your program has to be the/a STA thread and I think this provides the cleanest separation. If your async Main has no further useful work to do you may want to share a TaskCompletionSource between Main and the message-loop-running thread that then signals completion once Application.Run() returns.

4
On

STAThread does not work with async main.

This is a known and open issue.

The problem is that the code that transforms async main in to a "normal" main doesn't copy the attribute over.

Here is more information specifically about the un-copied attributes.

In some of the discussion they refer to using a SingleThreadedSynchronizationContext to allow for async code to be idiomatically used in an STA thread.

1
On

Wanted to reply to https://stackoverflow.com/a/47553573/4640588 but comments don't allow new lines, making the code formatting looked bad.

So based off their post and the helpful link they supplied, in there it showed what the compiler was doing (https://github.com/dotnet/roslyn/issues/22112#issuecomment-329462480). So maybe doing that manually may work.

[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
}