3

I have the following winforms event

public MainForm()
{
    InitializeComponent();

    new Form().ShowDialog(); // This causes the problem
}

private async void MainForm_Load(object sender, EventArgs e)
{
    LoadingLabel.Text = "Initializing...";

    try
    {
        await Task.Delay(500);
    }
    catch (Exception ex)
    {
        MessageBox.Show("Error initializing");
        Environment.Exit(0);
    }

    Console.WriteLine(LoadingLabel.InvokeRequired);
}

Expectation: Program prints false.
Result: Program prints true.

It is my understanding that await should set the synchronization context back to the original and no Invoke should be required. However, this is not the case. Attempting to update LoadingLabel's Text property throws an InvalidOperationException. Am I missing something?

I am using .NET 4.5.2.

Hele
  • 1,558
  • 4
  • 23
  • 39
  • 1
    The code is printing `false` on my machine, unless I throw in `ConfigureAwait(false)` of course. Can you reproduce this with just the code you've posted or is there anything else that might be affecting the result? – JSteward Apr 05 '17 at 01:37
  • I have global variables and other functions+events, but all the code in that function is shown. The issue occurs for me with the exact code above. – Hele Apr 05 '17 at 01:41
  • @JSteward You were right. I have edited the question to include the problematic code. – Hele Apr 05 '17 at 01:57
  • `ShowDialog` isn't something typically used in a constructor because it will block until the dialog is closed. If you can wait to show your dialog until `MainForm` is loaded moving the call to `MainForm_Load` will remove the need for an `Invoke` – JSteward Apr 05 '17 at 02:04
  • I need my dialog to show before the form appears. I do this in the constructor so as to set a readonly variable. Could you explain why my code results in this unexpected behavior? – Hele Apr 05 '17 at 02:08

1 Answers1

2

After the call to ShowDialog, which creates a nested message loop the WindowsFormsSyncronizationContext is replaced with the default SyncronizationContext causing you to need an Invoke. The context is then later restored. Further reading How to get a Synchronization Context for the second form shown

You have some options:

(1) Structure your code so that the call to ShowDialog occurs in the Load event or in the OnLoad override. I think this is the best approach and would serve you well long term.

(2) However, you can also do this:

public MainForm() {
    InitializeComponent();
    var uiContext = SynchronizationContext.Current;
    new Form().ShowDialog();
    SynchronizationContext.SetSynchronizationContext(uiContext);
}

This simply resets the SyncronizationContext back when the dialog is closed.

Community
  • 1
  • 1
JSteward
  • 6,833
  • 2
  • 21
  • 30