Events not happening in order

Viewed 126

I am creating a WinForms application in C#. When I click a button, a certain flow of events is supposed to transpire:

  • Click Button
  • Show label1
  • Show label2
  • Call function to parse a string the user entered before (this can take awhile depending on the string)
  • Show listBox1 and progressBar1
  • backgroundWorker1.RunWorkerAsync
  • backgroundWorker1_DoWork() does something x number of times and reports progress each time
  • backgroundWorker1_ProgressChanged() updates progressBar1 and adds an item to listBox1
  • backgroundWorker1_RunWorkCompleted() shows a message box saying "DONE"

But that is not what actually happens. When I trace through the code and look at the form it has several problems.

  1. label1 and label2 do not actually appear until after the parsing is done.
  2. progressBar1 only sometimes gets updated as ProgressChanged gets called. Other times it will wait until after "DONE" is printed and update all at once.
  3. Each time progressChange() gets called the vertical scroll bar on listBox1 gets smaller so I can tell Items are being added, but the text of the Items does not appear until after "DONE" is printed.

I am new to using backgroundWorker, so it's possible I just don't understand how it is supposed to function. But the delay of showing the labels I just don't understand at all. There are no errors when I trace through the code and the lines appear to be executed in the correct order.

Does anyone have ideas about what could be causing these issues? I would appreciate any help or advice. I'd rather not post my code, just because there is kind of a lot, but if anyone needs it to better understand, just lmk.

EDIT: Here is the code.

private void button1_Click(object sender, EventArgs e){
    label1.Show();
    label2.Show();
    String errMsg = parseString();
    if (errMsg == ""){
        listBox1.Items.Clear();
        listBox1.Show();

        progressBar1.Maximum = 100;
        progressBar1.Step = 1;
        progressBar1.Value = 0;
        progressBar1.Show();

        backgroundWorker1.DoWork += backgroundWorker1_DoWork;
        backgroundWorker1.ProgressChanged += backgroundWorker1_ProgressChanged;
        backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.WorkerSupportsCancellation = true;

        if (backgroundWorker1.IsBusy != true)
        {
            backgroundWorker1.RunWorkerAsync();
        }
    }
}

private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        backgroundWorker1.ReportProgress(1, "Updating Devices");
        for (int i = 0; i < 100; i++)
        {
            //todo: do stuff

            //update progress
            backgroundWorker1.ReportProgress(i, "Device:" + i);
        }
    }

private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        progressBar1.Value = e.ProgressPercentage;
        listBox1.Items.Add(e.UserState);
    }

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        MessageBox.Show("DONE");
    }
1 Answers

Thanks to @HansPassant and @mjwills for their comments. They led me on the right track and made this solution possible.

In the end I decided to do two background workers to solve the issue of label1 and label2 not appearing until after the parsing was done. I use the first one to do the parsing and the second one to do the "do stuff" section. In the code you will see I had to use Invoke to edit the labels since that part now existed on a different thread.

I also realized that the "do stuff" before calling ProgressChanged is not immediate. I've been developing in pieces and hadn't yet implemented that code, but I know it will take at least 3 seconds for those actions to complete (partly because pinging is involved). So for now I have put a Sleep(3000) call in that loop to simulate how it will actually behave. This solved the weird progressbar1 and listbox1 behavior which was caused by eating up all the memory.

Here is how the code turned out:

private void button1_Click(object sender, EventArgs e)
    {
        if (backgroundWorker1.IsBusy != true)
        {
            backgroundWorker1.RunWorkerAsync();
        }
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        String errMsg = parseString();
        if (errMsg == "")
        {
            if (listBox1.InvokeRequired)
            {
                listBox1.Invoke(new MethodInvoker(delegate
                {
                    listBox1.Items.Clear();
                    listBox1.Show();
                }));
            }

            if (progressBar1.InvokeRequired)
            {
                progressBar1.Invoke(new MethodInvoker(delegate
                {
                    progressBar1.Maximum = 100;
                    progressBar1.Step = 1;
                    progressBar1.Value = 0;
                    progressBar1.Show();
                }));
            }

            if (backgroundWorker2.IsBusy != true)
            {
                backgroundWorker2.RunWorkerAsync();
            }
        }
        else
        {
            MessageBox.Show(errMsg);
        }
    }

    private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
    {
        backgroundWorker2.ReportProgress(1, "Updating Devices");
        for (int i = 0; i < 100; i++)
        {
            System.Threading.Thread.Sleep(3000);
            //do stuff

            backgroundWorker2.ReportProgress(i, "Device:" + i);
        }
    }

    private void backgroundWorker2_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (progressBar1.InvokeRequired)
        {
            progressBar1.Invoke(new MethodInvoker(delegate
            {
                progressBar1.Value = e.ProgressPercentage;
            }));
        }
        if (listBox1.InvokeRequired)
        {
            listBox1.Invoke(new MethodInvoker(delegate
            {
                listBox1.Items.Add(e.UserState);
            }));
        }
    }

    private void backgroundWorker2_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        MessageBox.Show("DONE");
    }
Related