Set ApartmentState for async void main
Asked Answered
S

4

13

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);
}
Shoon answered 29/11, 2017 at 12:59 Comment(2)
@Evk - you'll get a ThreadStateException since, by definition, the current thread has already started.Esmerolda
Yes sure, that did not make sense.Bandeen
A
4

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;
}
Abagael answered 3/1, 2020 at 17:0 Comment(3)
This almost got me there but after everything runs I get a COM object that has been separated from its underlying RCW cannot be used exception.Plassey
I haven't run into that error before, but this may help (suggests initiating the COM object in each thread): #1567517Abagael
It was happening in MS Test when running the test in debug mode. No error otherwise.Plassey
E
11

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.

Esmerolda answered 29/11, 2017 at 13:4 Comment(1)
To start a STA thread, use this code: var thread = new Thread(() => DoSomething()); thread.SetApartmentState(ApartmentState.STA); thread.Start();Sudd
W
5

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.

Waller answered 29/11, 2017 at 13:4 Comment(4)
Thanks, suspected this to be a bug, but couldn't find the work item myself.Muddleheaded
@JürgenSteinblock - I'm not sure this is a bug per-se. As I point out, with STA you promise to run a message pump. All variants of Application.Run are, by their nature, blocking until the message pump is told to exit - so either you don't call Application.Run() (or moral equivalent) and so aren't fulfilling the STA promise or you're going to block anyway and not benefit from async.Esmerolda
@JürgenSteinblock - or, at least, the bug is that the compiler should raise an error if you try to mark an async main with STAThread rather than copying the attribute.Esmerolda
@Esmerolda Well, the issue is marked as Bug in the dotnet/roslyn repo, so maybe this will be solved somehow. I agree, using async/await with the blocking Application.Run() does not make sense. But I only used it for code executing before Run() and I was not aware that this would change my apps apartement state. A compiler or runtime error would be better than just ignoring the attribute.Muddleheaded
A
4

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;
}
Abagael answered 3/1, 2020 at 17:0 Comment(3)
This almost got me there but after everything runs I get a COM object that has been separated from its underlying RCW cannot be used exception.Plassey
I haven't run into that error before, but this may help (suggests initiating the COM object in each thread): #1567517Abagael
It was happening in MS Test when running the test in debug mode. No error otherwise.Plassey
C
0

Wanted to reply to https://mcmap.net/q/863793/-set-apartmentstate-for-async-void-main 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
}
Chibcha answered 30/5, 2023 at 9:25 Comment(1)
It won't work because the MainAsync is still executed asynchronously. Creating a Thread with SetApartmentState works. I wrapped it in an extension method so I can do await Task.Factory.StartNew(() => .., ApartmentState.STA)Muddleheaded

© 2022 - 2025 — McMap. All rights reserved.