Depois de executar o BackgroundWorker () com sucesso, o resto do programa não é executado

Oct 05 2020

Estou usando um BackgroundWorker () para executar uma consulta de longa duração em segundo plano enquanto estou apresentando uma janela pop-up informando que minha execução está em execução.

Aqui está como eu chamo o bg_worker ()

using System.Windows;
using System.ComponentModel;
using System.Threading;
using System;
using System.IO;
using System.Data.SqlClient;
using System.Windows.Input;

namespace TestEnvironment
{
    /// <summary>
    /// Interaction logic for MainWindow.xaml
    /// </summary>

    public partial class ProgressBarTemplate : Window
    {
        private CreateProjectScreen _CreateProjectScreen;
        private LoginScreen _LoginScreen;

        public ProgressBarTemplate()
        {
            InitializeComponent();
        }

        public static int RunCalculationsMethod(string connectionstring, string foldername)
        {
            bool exists = Directory.Exists(foldername);

            if (!exists)
            {
                Directory.CreateDirectory(foldername);
            }

            try
            {
                using (SqlConnection sqlConnection = new SqlConnection(connectionstring))
                {
                    var calculations_query = "SELECT * FROM table1");

                    using SqlCommand sqlCommand = new SqlCommand(calculations_query, sqlConnection);

                    sqlConnection.Open();

                    sqlCommand.CommandTimeout = 60 * 10;

                    int NumbderOfRecords = sqlCommand.ExecuteNonQuery();

                    return NumbderOfRecords;
                }
            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return -100;
            }
        }

        private void Window_ContentRendered(object sender, EventArgs e)
        {
            BackgroundWorker worker = new BackgroundWorker();
            
            worker.DoWork += worker_DoWork;
            worker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
            worker.RunWorkerAsync();           
        }

        void worker_DoWork(object sender, DoWorkEventArgs e)
        {
            int IsSuccessful = RunCalculationsMethod("Server=localhost;Database=DB_Name;Integrated Security=SSPI", String.Format("C:\\folder_path\\"));
        }

        void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            // This is called on the UI thread when the DoWork method completes
            // so it's a good place to hide busy indicators, or put clean up code

            try
            {
                this.Close();
                MessageBox.Show("DQ Calculations completed successfully", "Information", MessageBoxButton.OK, MessageBoxImage.Information);
            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
            }
        }
    }
}

O código acima é colocado em uma janela chamada ProgressBarTemplate ()

E o que eu quero é chamar o background_worker no clique do botão, de um botão colocado na minha MainWindow

Então minha MainWindow tem o seguinte clique no botão

private void RunCalculationsButton_Click(object sender, RoutedEventArgs e)
        {
            //RunCalculationsMethod(SQLServerConnectionDetails(), String.Format("C:\\DQ_Findings_{0}", HomePageTab.Header.ToString().Split(" - ")[1]));
            try
            {
                Application.Current.Dispatcher.Invoke((Action)delegate
                {
                    ProgressBarTemplate win_progressbar = new ProgressBarTemplate();
                    win_progressbar.Show();
                    //RunCalculationsMethod(SQLServerConnectionDetails(), String.Format("C:\\DQ_folder_test\\Findings\\"));
                }); // The code runs up to this point.
                
                //The code below is not executed for a reason, which I am trying to solve with this question
                List<SucessfulCompletion> reportsucessfulcompletion = new List<SucessfulCompletion>();
                reportsucessfulcompletion = SuccessfulCalculationsTimestamp(SQLServerConnectionDetails());

                if (reportsucessfulcompletion[0].Result==1)
                {
                    //Enable is only if successfull
                    PreviewCalculationsButton.IsEnabled = true;
                    PreviewReportButton.IsEnabled = true;

                    //add textbox of sucess
                    TickButtonSymbolCalculations.Visibility = Visibility.Visible;
                    SQLSuccessfulTextCalculations.Visibility = Visibility.Visible;
                    XerrorSymbolCalculations.Visibility = Visibility.Hidden;
                    SQLFailedTextCalculations.Visibility = Visibility.Hidden;
                    SQLSuccessfulTextCalculations.Text = String.Format("Completed On: {0}", reportsucessfulcompletion[0].Timestampvalue);
                }
                else
                {
                    //add textbox of fail
                    TickButtonSymbolCalculations.Visibility = Visibility.Hidden;
                    SQLSuccessfulTextCalculations.Visibility = Visibility.Hidden;
                    XerrorSymbolCalculations.Visibility = Visibility.Visible;
                    SQLFailedTextCalculations.Visibility = Visibility.Visible;
                    SQLFailedTextCalculations.Text = String.Format("Failed On: {0}", reportsucessfulcompletion[0].Timestampvalue);
                }
            }

            catch (Exception ex)
            {
                MessageBox.Show(ex.ToString(), "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                return;
            }
        }

Ao clicar no botão, inicio o bg_worder chamando a janela ProgressBarTemplate (). Embora após completar a tarefa o código gere algum texto e habilite a visibilidade de alguns botões eles não são executados. Por que isso está acontecendo? Estou esquecendo de algo?

Respostas

3 PanagiotisKanavos Oct 05 2020 at 16:09

O código é um pouco confuso, então vou postar como deve ser feito.

BGW é uma classe obsoleta totalmente substituída por Task.Runand Progress desde 2012. Não há necessidade de usar BGW ou Invokequando async/awaitestiverem disponíveis.

Em vez de colocar os cálculos na janela de progresso, você pode usar um manipulador de eventos assíncrono, realizar quaisquer cálculos em segundo plano e atualizar a IU assim que a operação em segundo plano for concluída. O formulário de progresso não parece relatar nenhum progresso, então tudo o que é necessário é exibi-lo e ocultá-lo. O código pode ser tão simples como:

private async void RunCalculationsButton_Click(object sender, RoutedEventArgs e)
{
    var win_progressbar = new ProgressBarTemplate();
    win_progressbar.Show();
    try
    {
        //Update the UI
        var results=await Task.Run(()=> RunCalculationsMethod(...));
        //Update the UI
    }
    finally
    {
        win_progressbar.Close();
    }
}

O try/finallyé usado para garantir que o formulário seja fechado mesmo se houver um erro.

Relatório de progresso

O relatório de progresso está disponível por meio da classe Progress e da interface IProgress . IProgress<T>permite que uma tarefa em segundo plano envie uma mensagem fortemente tipada para a classe que implementa a interface. A Progress<T>implementação garante que a mensagem seja processada no encadeamento onde foi criada, normalmente o encadeamento de UI.

Supondo que este seja o tipo de mensagem:

class MyProgress
{
    public int Percent{get;set;}
    public string Message {get;set;}
} 

O RunCalculationsMethodpode ser modificado para aceitar e usar um IProgress<MyProgress>;

public static int RunCalculationsMethod(string connectionstring, string foldername, 
                                        IProgress<MyProgress> progress)
{

    progress.Report(new MyProgress{Percent=0,Message="Starting"};
    ....
    progress.Report(new MyProgress{Percent=100,Message="Finished"};
}

O manipulador de eventos só precisa criar um Progress<MyProgress>e fornecer um método para atualizar a IU. Vamos supor que ProgressBarTemplatetenha esse método, chamado Update(string,int):

private async void RunCalculationsButton_Click(object sender, RoutedEventArgs e)
{
    var win_progressbar = new ProgressBarTemplate();
    IProgress<MyProgress> pg=new Progress<MyProgress>(pg=>
                        win_progressbar.Update(pg.Message,pg.Percent));
    win_progressbar.Show();
    try
    {
        //Update the UI
        var results=await Task.Run(()=> RunCalculationsMethod(...,pg));
        //Update the UI
    }
    finally
    {
        win_progressbar.Close();
    }
}

Você pode encontrar uma explicação mais completa em Async em 4.5: Habilitando o andamento e o cancelamento em APIs Async

TaehyungKim Oct 05 2020 at 15:51

Você deve usar Application.Current.Dispatcher.Invoke porque tenta acessar o elemento de interface do usuário. Exceto o thread principal, outro thread não pode acessar o elemento ui.

Substitua seu código para

Dispatcher.Invoke(() =>{
 // UI access code
});