BackgroundWorker.RunWorkerAsync() 不进入后台

BackgroundWorker.RunWorkerAsync() doesn't go background

我有与数据库一起工作的 WPF 应用程序。我有 Connect to DB 按钮。当用户单击该按钮时,我想在应用程序尝试连接到数据库时显示一些进度动画。 这是我的按钮和进度动画:

<Button Grid.Column="2" Grid.ColumnSpan="2" Grid.Row="2" Name="btnLogin" HorizontalAlignment="Center" VerticalAlignment="Center" Click="btnLogin_Click" Margin="5">
    <StackPanel Orientation="Horizontal">
        <Image x:Name="btnLoginIcon" Source="pack://application:,,,/Content/Images/Connect_Icon.png" Width="20" Height="20"/>
        <TextBlock x:Name="btnLoginText" Text="Connect" VerticalAlignment="Center" />
    </StackPanel>
</Button>
<mui:ModernProgressRing Grid.Column="2" Grid.ColumnSpan="2" Grid.Row="2" x:Name="ConnectProgressRing" Width="40" Height="40" Visibility="Hidden" IsActive="True" Style="{StaticResource ThreeBounceProgressRingStyle}" />

这是我的后台工作代码:

private void btnLogin_Click(object sender, RoutedEventArgs e)
{
    BackgroundWorker bw = new BackgroundWorker();
    bw.DoWork += new DoWorkEventHandler(bw_DoWorkConnect);
    bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerConnectCompleted);

    ConnectProgressRing.Visibility = Visibility.Visible;
    btnLogin.Visibility = Visibility.Hidden;
    bw.RunWorkerAsync();
}

private void LoginToDB()
{
    string ServerPath = Server.Text;
    string Database = Schema.Text;

    dbStr = "Server=" + ServerPath + ";Database=" + Database + ";Trusted_Connection=True;";
    try
    {
        using (SqlConnection conn = new SqlConnection(dbStr))
        {
            conn.Open();
            conn.Close();
        }
    }
    catch (Exception ex)
    {
        if (ex.Message != null)
        {
            MessageBoxButton btn = MessageBoxButton.OK;
            ModernDialog.ShowMessage(ex.Message, "Failure to connect", btn);
        }
    }
}

void bw_DoWorkConnect(object sender, DoWorkEventArgs e)
{
    Dispatcher.Invoke(new Action(() => {
        LoginToDB();
    }), DispatcherPriority.ContextIdle);
}

void bw_RunWorkerConnectCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    Dispatcher.Invoke(new Action(() =>
    {
        ConnectProgressRing.Visibility = Visibility.Hidden;
        btnLogin.Visibility = Visibility.Visible;
    }), DispatcherPriority.ContextIdle);
}

出于某种原因,当我单击“连接”按钮时出现错误的服务器和数据库时,我的 window 冻结并且在收到错误消息之前我无法执行任何操作。你知道为什么我的 LoginToDB() 没有进入后台吗?

我不确定 bw_RunWorkerConnectCompleted 如何与所有内容联系在一起,但 WPF 可以使用 async/await 模式。这允许在不同的线程上继续执行,并且 UI 线程被释放,因此您的 windows 不会 freeze/hang。这是您重构的代码以使用该模式。同样,我不知道 bw_RunWorkerConnectCompleted 所以我暂时删除了它。

private async Task LoginToDBAsync() // notice the async keyword in the method signature
{
    string ServerPath = Server.Text;
    string Database = Schema.Text;

    dbStr = "Server=" + ServerPath + ";Database=" + Database + ";Trusted_Connection=True;";
    try
    {
        using (SqlConnection conn = new SqlConnection(dbStr))
        {
            await conn.OpenAsync(); // will release the thread back to the WPF app and connection continues on new thread until it completes
            conn.Close();
        }
    }
    catch (Exception ex)
    {
        if (ex.Message != null)
        {
            MessageBoxButton btn = MessageBoxButton.OK;
            ModernDialog.ShowMessage(ex.Message, "Failure to connect", btn);
        }
    }
}

// previous name was async void bw_DoWorkConnect(object sender, DoWorkEventArgs e)
async void btnLogin_Click(object sender, RoutedEventArgs e) // notice the async keyword in the method signature. Because its a WPF event callback it returns void and not Task like the above method
{
    ConnectProgressRing.Visibility = Visibility.Visible;
    btnLogin.Visibility = Visibility.Hidden;

    await LoginToDBAsync(); // await db call

    // ui code to be executed after the previous call completes.
    // the continuation is executed on the UI thread
    ConnectProgressRing.Visibility = Visibility.Hidden;
    btnLogin.Visibility = Visibility.Visible;

}

这是因为您的异步代码调用了对调度程序线程的调用

Dispatcher.Invoke(new Action(() => {
        LoginToDB();
    }), DispatcherPriority.ContextIdle);

调度程序将使用与 UI 相同的线程,您的 UI 将冻结。您需要删除 Dispatcher.Invoke 以允许您的代码异步 运行,尽管我猜您已经把它放在那里是因为 LoginToDB 引用了一些 UI 元素?

void bw_DoWorkConnect(object sender, DoWorkEventArgs e)
{
    LoginToDB();
}

如果你想更新 UI 那么不要在 DoWork 事件处理程序中捕获异常并让 RunWorkerCompleted 事件处理程序检查 RunWorkerCompletedEventArgs 的错误以更新按钮和显示消息等

private void LoginToDB()
{
    string ServerPath = Server.Text;
    string Database = Schema.Text;

    dbStr = "Server=" + ServerPath + ";Database=" + Database + ";Trusted_Connection=True;";
    using (SqlConnection conn = new SqlConnection(dbStr))
    {
        conn.Open();
        conn.Close();
    }
}

您不需要在 RunWorkerCompleted 事件处理程序中使用 Dispatcher.Invoke,因为该方法将在 UI 线程上执行。

void bw_RunWorkerConnectCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if (e.Error != null)
    {
        MessageBoxButton btn = MessageBoxButton.OK;
        ModernDialog.ShowMessage(e.Error.Message, "Failure to connect", btn);
        return;
    }

    ConnectProgressRing.Visibility = Visibility.Hidden;
    btnLogin.Visibility = Visibility.Visible;
}