UI Freezes when calling .Show on new Window

Viewed 172

In my application, I open the MainWindow from LoginWindow. When I call MainWindow.Show(), LoginWindow UI freezes. I've tried:

  • await Task.Run() but this won't work because I need to open the window on the UI thread

  • await Application.Current.Dispatcher.BeginInvoke but this doesn't seem to work asynchronously

I must be misunderstanding something fundamental about how async programming works.

public async void LoginAsync(object pbx)
    {
        VisLoginLoading = Visibility.Visible;
        var passwordBox = pbx as PasswordBox;
        string enteredPassword = passwordBox.Password;

        string cmdQuery = $"SELECT USERNAME, PASSWORD, AccessLevel, ALERTS, STORES FROM Tbl_UserAccounts WHERE USERNAME = @Username";

        try
        {
            string errorMsg = await Task.Run(() =>
            {
                using (SqlConnection con = new SqlConnection(PosConString))
                using (SqlCommand cmd = new SqlCommand(cmdQuery, con))
                {
                    cmd.Parameters.AddWithValue("@Username", SqlDbType.NVarChar).Value = Username;
                    cmd.CommandTimeout = 30;
                    con.Open();
                    SqlDataReader reader = cmd.ExecuteReader();

                    if (reader.HasRows)
                    {
                        while (reader.Read())
                        {
                            string password = reader.GetString(1);

                            if (DoesPasswordMatch(password, enteredPassword))
                            {
                                UserAccount.Username = (reader[0] == DBNull.Value) ? null : reader.GetString(0);
                                UserAccount.Access = Enum.IsDefined(typeof(UserAccess), reader.GetInt32(2)) ? (UserAccess)reader.GetInt32(2) : UserAccess.Unknown;
                                UserAccount.Alerts = (reader[3] == DBNull.Value) ? false : reader.GetBoolean(3);
                                UserAccount.Stores = (reader[4] == DBNull.Value) ? null : HelperMethods.ReturnWordList(reader.GetString(4));
                                return String.Empty;
                            }
                            else
                            {
                                return "Password is incorrect";
                            }
                        }
                    }
                    else
                    {
                        return "User does not exist";
                    }
                }

                return "Failed to login";
            });

            if (String.IsNullOrEmpty(errorMsg))
            {
                LoadApp();
            }
            else
            {
                MessageBox.Show(errorMsg);
            }
        }
        catch (Exception ex)
        {
            MessageBox.Show($"Failed to login\n\n{ex}");
        }

        VisLoginLoading = Visibility.Hidden;
    }

    public async void LoadApp()
    {
        VisLoginLoading = Visibility.Visible;

        await Application.Current.Dispatcher.BeginInvoke(new Action(() =>
            {
                MainWindow mainWindow = new MainWindow();
                mainWindow.Show();
            }));

        foreach (Window window in Application.Current.Windows)
        {
            if (window.Title == "Login") window.Close();
        }
        VisLoginLoading = Visibility.Hidden;
    }
1 Answers

It's logically, that GUI thread freezes.
Look, you start a long running operation on GUI thread with Application.Current.Dispatcher.BeginInvoke. So all other "user" of this thread have to wait until it's available, to handle their(LoginWindow) operations on it. What you can do in this situation?

You can start another GUI thread, which will take care about new Window:

public void LoadApp()
{
    VisLoginLoading = Visibility.Visible;

    var thr = new Thread(() =>
    {
        var mw = new MainWindow();
        mw.Show();
        mw.Closed += (sender, e) => mw.Dispatcher.InvokeShutdown();
        System.Windows.Threading.Dispatcher.Run();
    });
    thr.SetApartmentState(ApartmentState.STA);
    thr.Start();

    //Move this logic to the MainWindow, after initialization is done.
    //foreach (Window window in Application.Current.Windows)
    //{
    //    if (window.Title == "Login") window.Close();
    //}
    VisLoginLoading = Visibility.Hidden;
}

Now you can start a MainWindow, but you have to solve another challenge (I think) - notify VisLoginLoading the initialization is finished. But it's another question. So your UI will not freez.
See also Threading Model.

Update:

In MainWindow use:

Application.Current.Dispatcher.BeginInvoke((Action)(()=>
{
    foreach (Window window in Application.Current.Windows)
    {
        if (window.Title == "Login") window.Close();
    }
}));
Related