Работа с backgroundworker c#

544
05 апреля 2017, 12:59

Всем добрый день! В программе потребовалось реализовать отправку запроса в БД и получение ответа в отдельном потоке, решил обратиться к backgroundworker. При открытии второй формы в программе и клике на чекбоксе нужно запустить третью форму (с прогрессбаром и таймером) и в воркере получить таблицу из базы, после получения отобразить в датагриде на второй форме и закрыть третью. Написал следующий код, но работает странно - датагрид не обновляется и третья форма не закрывается автоматически. В чем косяк? Также планировал добавить возможность прервать работу воркера из 3 формы по клику на кнопку "Прервать", но пока остановился на этом, через Threads сделать получилось, но хочется более верно реализовать (закомментированные куски кода остались от другой идеи)

public partial class Organizations : Form
{
    Progress ThirdForm;
    public Thread potok;
    public Thread potok1;
    public BackgroundWorker BW;
    public Organizations()
    {
        InitializeComponent();
        backgroundWorker1.DoWork +=
            new DoWorkEventHandler(backgroundWorker1_DoWork);
        backgroundWorker1.RunWorkerCompleted +=
            new RunWorkerCompletedEventHandler(
        backgroundWorker1_RunWorkerCompleted);
        backgroundWorker1.ProgressChanged +=
            new ProgressChangedEventHandler(
        backgroundWorker1_ProgressChanged);
        backgroundWorker1.WorkerSupportsCancellation = true;
    }
    public delegate void Del();
    DataTable Loading(BackgroundWorker worker, DoWorkEventArgs e)
    {
        bool b2 = false;
        DataTable dt;
        if (worker.CancellationPending)
        {
            e.Cancel = true;
        } 
        else
        { 
            // Получение строки соединения с базой данных и заполнение таблицы на CalculationForm
            var cn = new SqlConnection();
            SqlDataReader myReader = null;
            cn.ConnectionString = ConnString.GetConnectionString();
            cn.Open();
            //  Идентификатор расчета
            if (((Reports)(Application.OpenForms[0])).textBox1.Text == "")
            {
                MessageBox.Show("Необходимо сначала выбрать расчет!", "Обработчик кнопки Организации", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return dt = null;
            }
            else
            {
                int IdCostsResultTask = Convert.ToInt32(((Reports)(Application.OpenForms[0])).textBox1.Text);
                string sqlQuery = @" SELECT cr.ExportIdListEdu 'ExportIdListEdu', ReestrEdu.IdListEdu 'IdListEdu',slovar..ListEdu.Name 'ShortName', slovar..ListEdu.LongName 'Name' FROM ReestrEdu " +
                                   " LEFT JOIN slovar..ListEdu on ReestrEdu.IdListEdu = slovar..ListEdu.IdListEdu " +
                                   " LEFT JOIN CostsResult as cr on ReestrEdu.IdListEdu = cr.ExportIdListEdu or ReestrEdu.IdListEdu = cr.IdListEdu" +
                                   " WHERE cr.IdCostsResultTask = " + IdCostsResultTask + " and slovar..ListEdu.LongName is not null " +
                                   " Group By cr.ExportIdListEdu, ReestrEdu.IdListEdu, slovar..ListEdu.Name, slovar..ListEdu.LongName  ORDER BY 1,2 ";
                var myCommand = new SqlCommand(sqlQuery, cn);
                myCommand.CommandTimeout = 600;
                myReader = myCommand.ExecuteReader();
                using (myReader)
                {
                    var table = new System.Data.DataTable();
                    table.Load(myReader);
                    dt = table;
                }
                myReader.Dispose();
                cn.Close();
                b2 = true;
            }
        }
        return dt;   
    }
    DataTable Loading2(BackgroundWorker worker, DoWorkEventArgs e)
    {
        bool b2 = false;
        DataTable dt2;
        if (worker.CancellationPending)
        {
            e.Cancel = true;
        } 
        else
        { 
            // Получение строки соединения с базой данных и заполнение таблицы на CalculationForm
            var cn = new SqlConnection();
            SqlDataReader myReader = null;
            cn.ConnectionString = ConnString.GetConnectionString();
            cn.Open();
            //  Идентификатор расчета
            if (((Reports)(Application.OpenForms[0])).textBox1.Text == "")
            {
                MessageBox.Show("Необходимо сначала выбрать расчет!", "Обработчик кнопки Организации", MessageBoxButtons.OK, MessageBoxIcon.Error);
                return dt2 = null;
            }
            else
            {
                int IdCostsResultTask = Convert.ToInt32(((Reports)(Application.OpenForms[0])).textBox1.Text);
                string sqlQuery = @" SELECT cr.ExportIdListEdu 'ExportIdListEdu', ReestrEdu.IdListEdu 'IdListEdu',slovar..ListEdu.Name 'ShortName', slovar..ListEdu.LongName 'Name' FROM ReestrEdu " +
                                   " LEFT JOIN slovar..ListEdu on ReestrEdu.IdListEdu = slovar..ListEdu.IdListEdu " +
                                   " LEFT JOIN CostsResult as cr on ReestrEdu.IdListEdu = cr.ExportIdListEdu" +
                                   " WHERE cr.IdCostsResultTask = " + IdCostsResultTask + " and slovar..ListEdu.LongName is not null " +
                                   " Group By cr.ExportIdListEdu, ReestrEdu.IdListEdu, slovar..ListEdu.Name, slovar..ListEdu.LongName  ORDER BY 1 ";
                var myCommand = new SqlCommand(sqlQuery, cn);
                myCommand.CommandTimeout = 600;
                myReader = myCommand.ExecuteReader();
                using (myReader)
                {
                    var table = new System.Data.DataTable();
                    table.Load(myReader);
                dt2 = table;
                }
                myReader.Dispose();
                cn.Close();
                b2 = true;
            }  
        }
        return dt2;
    }
    private void checkBox1_CheckedChanged(object sender, EventArgs e)
    {
        if (backgroundWorker1.IsBusy != true)
        {
            // Start the asynchronous operation.
            backgroundWorker1.RunWorkerAsync();
        }
    }
    private void OrgDatagrid_CellContentClick(object sender, DataGridViewCellEventArgs e)
    {
        ((Reports)(Application.OpenForms[0])).listBox1.Items.Insert(0, "1");  
        this.Close();
    }
    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;
        if (checkBox1.Checked)
        {
            ThirdForm = new Progress();
            ThirdForm.ShowDialog();
            e.Result = Loading(worker, e);
        }
        else
        {
            ThirdForm = new Progress();
            ThirdForm.ShowDialog();
            e.Result = Loading2(worker, e);
        }
    }
    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        if (e.Error != null)
        {
            MessageBox.Show(e.Error.Message);
        }            
        else
        {
            ThirdForm.Close();
            OrgDatagrid.DataSource = e.Result;
        }
    }
    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
    }
}

Код 3 формы (с таймером, прогрессбаром и кнопкой отмены асинхронной операции):

 public partial class Progress : Form
{

    public Progress()
    {
        InitializeComponent();
        // Обрабатываем событие таймера.
        timer1.Interval = 1000;
        timer1.Start();
    }
    public void timer1_Tick(object sender, EventArgs e)
    {
        TimeLabel.Text = DateTimer.ToString("mm:ss");
        DateTimer = DateTimer.AddSeconds(1);
    }
    private void BtnCancel_Click(object sender, EventArgs e)
    {
        Organizations f = new Organizations();            
        if (f.backgroundWorker1.WorkerSupportsCancellation == true)
        {
            // Cancel the asynchronous operation.
            f.backgroundWorker1.CancelAsync();
        }
    }

UPD: Решил проблему, перенеся вызов ThirdForm в метод checkBox1_CheckedChanged после вызова воркера. Беда была именно в вызове 3 формы из backgroundWorker1_DoWork. Внутри методов worker эти обращения работают некорректно (по крайней мере, не так, как хотелось бы). Однако осталась другая проблема - кнопка отмены операции в воркере с 3 формы не срабатывает (просто ничего не происходит). Пока не понимаю, как корректно вызвать завершение worker из другой формы.

UPD2: Проблема с кнопкой решена следующим образом:

Изменения в коде на 3 форме:

public event EventHandler Cancel = delegate { };
private void BtnCancel_Click(object sender, EventArgs e)
    {
        Cancel(sender, e);
        timer1.Stop();           
    }

Обработка во второй форме:

void CancelProcess(object sender, EventArgs e)
    {
        backgroundWorker1.CancelAsync();
        if (checkflag)
        {
            checkBox1.Checked = false;
            ThirdForm.Close();
            ThirdForm.timer1.Stop();
            ThirdForm.TimeLabel.Text = "";
            ThirdForm.DateTimer = new DateTime(0, 0);
        }
        else
        {
        checkBox1.Checked = true;
        //backgroundWorker1.Dispose();            
        ThirdForm.Close();
        ThirdForm.timer1.Stop();
        ThirdForm.TimeLabel.Text = "";
        ThirdForm.DateTimer = new DateTime(0, 0);
        }
    }
private void checkBox1_CheckedChanged(object sender, EventArgs e)
    {
        if (backgroundWorker1.IsBusy != true)
        {
            // Start the asynchronous operation.
            checkflag = checkBox1.Checked;
            ThirdForm = new Progress();
            ThirdForm.timer1.Stop();
            ThirdForm.TimeLabel.Text = "";
            ThirdForm.Cancel += CancelProcess;
            backgroundWorker1.RunWorkerAsync();
            ThirdForm.timer1.Start();
            ThirdForm.ShowDialog();
        }
    }
Answer 1

Форма должна закрыться или вывестись сообщение об ошибке (либо MessageBox, либо программа упадет). Ошибка может не появиться, если возникла не в основном потоке (например, если backgroundWorker1_RunWorkerCompleted сработал в другом потоке).

предлагаю на всякий случай вставить Invoke внутрь этого метода

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    this.Invoke(new Action(() =>
       if (e.Error != null)
       {
           MessageBox.Show(e.Error.Message);
       }            
       else
       {
           ThirdForm.Close();
           OrgDatagrid.DataSource = e.Result;
           MessageBox.Show("Работа завершена");
       }
   ));
}

Заодно добавил лишний MessageBox, чтобы убедиться, что backgroundWorker1_RunWorkerCompleted вобще срабатывает.

Что касается прерывания BackgroundWorker с помощью метода Abort(), как для Thread, то, к сожалению, он его не поддерживает. Конечно же делать Abort() нежелательно, т.к. остается мертвый Connection. Хотя сборщик мусора его потом закроет.

Думаю есть смысл полностью отказаться от BackgroundWorker и все реализовать на Thread, помещая весь код работы с интерфейсом в Invoke() (формы нельзя менять в другом потоке).

READ ALSO
Как убрать привязку компонентов к panel?

Как убрать привязку компонентов к panel?

Происходит такая ситуация, что когда я перетягиваю panel2 на panel1, panel2 не отображается на весь размер, а только на тот какой размер у panel1Как это...

234
Во что компилируется контроллер?

Во что компилируется контроллер?

Допустим, я сделал правки только в контроллере и не хочу по новой разворачивать сайтКуда компилируется контроллер и можно ли обновить только...

241
Управление кнопками програмно

Управление кнопками програмно

Конвертировал код из vbnet на c#

285
Переехать с entity framework 4 на последний

Переехать с entity framework 4 на последний

Подскажите, как происходит переход с более раннего EF на более новый и какие трудности при этом могут возникнуть?

239