4

The normal behavior of using await to call an asynchronous method is that its context stays the same as shown in the example below:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private async void Form1_Load(object sender, EventArgs e)
    {
        //main thread here
        await SampleAsync();
        //stays as main thread
        var g = 10;
    }

    static async Task SampleAsync()
    {
        await Task.Delay(1000);
    }
}

But when I use this in the Winforms Main console method call, this behavior is no longer true: After calling the await method, the thread change from the main thread to the worker thread. Why is this? I would like for it to stay on the Main (UI Thread) after the await call.

    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        MainAsync().Wait();
    }

    static async Task MainAsync()
    {
        Application.Run(new Form1());
        //Main Thread here
        await Task.Delay(1000);
        //Why does it turn in to worker thread here? ConfigureAwait(false) is not used here?!
        var g = 5;
        //Run other Application.Run() if certain conditions apply; but I need to be in the Main Thread.
    }

In case you are wondering what I am trying to achieve, I am trying to catch the errors in the async method, MainAsync, rather than Main so that I can avoid having to disentangle errors from the AggregateException (https://msdn.microsoft.com/en-us/magazine/JJ991977.aspx; Figure4). I also want to stay in the UI thread so I can run other Application.Run in the UI thread.

7
  • Try moving the code into a button click event handler instead of form load. Also, see my own question about what happens when Application.Run or Form1.ShowDialog returns here: stackoverflow.com/questions/32439669/… Commented Sep 9, 2015 at 21:02
  • Why did you make your MainAsync async anyway? It is synchronous by nature because it keeps the process alive. Commented Sep 9, 2015 at 21:03
  • The code working in the form1_load as working as expected, as the context remains the same. Not sure what moving the code to a button click event will achieve? Commented Sep 9, 2015 at 21:04
  • @usr In my real application, after "Form1" runs, I call an async method to do some work in MongoDb (The latest mongo c# driver db calls are async by nature and I have let it bubbled up). That is what the Task.Delay is supposed to represent. Commented Sep 9, 2015 at 21:08
  • @geohnH and you need to stay on the same thread? In that case set up a single threaded sync context that you control. Nito async has such a class. But why is your code thread-affine? Commented Sep 9, 2015 at 21:09

1 Answer 1

2

Because Application.Run is what "Runs" the continuation on the main thread, once Application.Run returned SynchronizationContext.Current was unset for the "UI Thread" therefor the await had to use the default context which is the ThreadPool.

Because you have no UI at that point I would recommend doing a synchronous wait (.Wait()) instead of a await at that point, that will make sure you are still on the same STA thread for your later Application.Run calls.

EDIT: Did not see your mention about disentangling AggregateException, all await is doing with the AggregateException is doing the equivalent of

catch(AggregateException ex)
{
    ExceptionDispatchInfo.Capture(ex.InnerException).Throw();
}

Which causes the first exception of the AggregateException to be re-raised without messing up it's stack trace.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.