Search code examples
c#wpf.net-4.5backgroundworkersqlconnection

BackgroundWorker.RunWorkerAsync() doesn't go background


I have WPF application that works with DB. I have Connect to DB button. When user clicks that button I want to show some progress animation while app is trying to connect to DB. Here's my button and progress animation:

<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}" />

Here's my code for background work:

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

For some reason when I click Connect button and I have wrong Server and DB my window freezes and I can't do anything until I got error message back. Do you have any ideas why is my LoginToDB() doesn't go in background?


Solution

  • I am not sure how bw_RunWorkerConnectCompleted ties in with everything but WPF has the ability to use the async/await pattern. This allows execution to continue on a different thread and the UI thread gets released so your windows does not freeze/hang. Here is your code refactored to make use of that pattern. Again, I have no idea about bw_RunWorkerConnectCompleted so I removed it for now.

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