【问题标题】:UI Freezes when calling .Show on new Window调用时 UI 冻结。在新窗口上显示
【发布时间】:2019-10-28 12:02:33
【问题描述】:

在我的应用程序中,我从 LoginWindow 打开 MainWindow。当我调用 MainWindow.Show() 时,LoginWindow UI 冻结。我试过了:

  • await Task.Run() 但这不起作用,因为我需要在 UI 线程上打开窗口

  • 等待 Application.Current.Dispatcher.BeginInvoke 但这似乎不能异步工作

我一定误解了异步编程的基本原理。

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

【问题讨论】:

  • 评论不用于扩展讨论;这个对话是moved to chat
  • 认为这是因为 Dispatcher 和当前帧。您的主窗口在调度程序上推送了一个框架,现在阻止了登录窗口框架。

标签: c# wpf asynchronous


【解决方案1】:

从逻辑上讲,GUI 线程会冻结。
看,您使用Application.Current.Dispatcher.BeginInvoke 在 GUI 线程上启动了一个长时间运行的操作。所以这个线程的所有其他“用户”必须等到它可用,才能处理他们的(LoginWindow)操作。在这种情况下你能做什么?

您可以启动另一个 GUI 线程,该线程将处理新窗口:

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

现在您可以启动MainWindow,但您必须解决另一个挑战(我认为)- 通知VisLoginLoading 初始化完成。但这是另一个问题。所以你的用户界面不会冻结。
另见Threading Model.

更新:

在主窗口中使用:

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

【讨论】:

  • 这对我不起作用。 MainWindow InitializeComponent 发生错误:调用线程无法访问此对象,因为不同的线程拥有它。
  • 如果通过访问Application.Current.Windows 发生错误,则使用Application.Current.Dispatcher.BeginInvoke 进行此循环。查看我的更新。
  • 当我完全按照上面的代码使用时会发生错误。我不确定你说的需要改变什么?
  • 使用上面的代码,我可以从 Window1 启动 Window2 并在长时间运行(使用 Thread.Sleep)完成 Window2 初始化后关闭 Window1。 Windows2 中的对象访问没有问题接受Application.Current.Windows。我已经使用Application.Current.Dispatcher.BeginInvoke 解决了这个问题。如果不起作用,请发送MCVE
  • 在测试应用程序中,您的代码可以工作,但由于某种原因,它在我的主应用程序中不起作用。我无法重新创建问题(还)。你的回答在技术上确实回答了我的问题,所以我会标记它并投票
猜你喜欢
  • 1970-01-01
  • 2016-04-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-06-06
  • 1970-01-01
  • 1970-01-01
  • 2017-05-24
相关资源
最近更新 更多