0

I'm having an issue with a form that uses a background worker thread to fetch data and update a datagrid.

The use case is a list with progress bars that shows how much tables have loaded in a database.

It goes in the following order,

  • FormA instantiates FormB
  • FormB kicks of a background worker that loops to fetch data and update a datagrid.datasource.

It works nicely sometimes but others the FormB.ShowDialog() comes back with a null reference exception. I can't understand why.

Here's the calling form code:

 private void button1_Click(object sender, EventArgs e)
    {


        using (var f = new FormFactProgress(_dwConnectionString, _sourceConnectionString, _etlConnectionString))
        {
            f.ShowDialog();  \\ THIS IS WHERE THE NULL REFERENCE EXCEPTION ALWAYS HAPPENS!
            labelLoadWarning.Visible = false;
            groupBoxLoadFacts.Enabled = false;
            buttonLoadFacts.BackColor = Color.FromArgb(128, 255, 128);
            buttonLoadFacts.Text = "Loaded";
            if (dynamicWarehouseCatalog.FactsLoaded(_dwConnectionString) && dynamicWarehouseCatalog.DimsLoaded(_dwConnectionString))
            {
                groupBoxSync.Enabled = true;
            }
        }

    }

The child form (It's the second backgroundworker that does the datagrid update):

private void buttonStart_Click(object sender, EventArgs e)
    {
        backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
        if (!backgroundWorker1.IsBusy)
        {
            backgroundWorker1.RunWorkerAsync();
        }
        backgroundWorker2.DoWork += new DoWorkEventHandler(backgroundWorker2_DoWork);
        if (!backgroundWorker2.IsBusy)
        {
            backgroundWorker2.RunWorkerAsync();
        }
        buttonStart.Enabled = false;


    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {
            BackgroundWorker worker = (BackgroundWorker)sender;

            sSISDBCatalog.StartFactLoad(_etlConnectionString);

            while (isLoading)
            {
                System.Threading.Thread.Sleep(1000);
                if (dynamicWarehouseCatalog.FactsLoaded(_dwConnectionString))
                {
                    if (dynamicWarehouseCatalog.AllFactsLoaded(_dwConnectionString))
                    {
                        isLoading = false;
                    }
                }
            }
        }
        catch(Exception ex)
        {
            MessageBox.Show(
           "Progress viewing has failed. The ETL is still running though. Please monitor your data warehouse growth manually. Error: " + ex, "Pregress Visualisation Failed",
           MessageBoxButtons.OK,
           MessageBoxIcon.Error);
            return;
        }


    }

    private void backgroundWorker2_DoWork(object sender, DoWorkEventArgs e)
    {
        try
        {
            Control.CheckForIllegalCrossThreadCalls = false;
            BackgroundWorker worker = (BackgroundWorker)sender;
            var dw = DwData(_dwConnectionString);
            var source = SourceData(_sourceConnectionString);
            dataGridView1.DataSource = GetProgress(dw, source);
            while (isLoading)
            {
                System.Threading.Thread.Sleep(1000);
                dw = DwData(_dwConnectionString);
                dataGridView1.DataSource = GetProgress(dw, source);
                dataGridView1.Refresh();
            }

            MessageBox.Show(
               "Your data warehouse facts are loaded!", "Facts Loaded",
               MessageBoxButtons.OK,
               MessageBoxIcon.Information);
            return;
        }
        catch (Exception ex)
        {
            MessageBox.Show(
           "Progress viewing has failed. The ETL is still running though. Please monitor your data warehouse growth manually. Error: " + ex, "Pregress Visualisation Failed",
           MessageBoxButtons.OK,
           MessageBoxIcon.Error);
            return;
        }

    }
Rishav
  • 3,095
  • 25
  • 45
Evan Barke
  • 89
  • 1
  • 10
  • 1
    Why are you using background worker and not a modern approach like async/await? – rory.ap May 18 '18 at 12:01
  • @rory.ap I just happened to choose this as the solution. I'm happy to change it if there is a better approach. Like I said, when this weird error doesn't occur it works pretty well. – Evan Barke May 18 '18 at 12:03
  • Don't update your UI directly from the background worker thread: use `Invoke` or `backgroundWorker.ReportProgress()` etc. instead. `CheckForIllegalCrossThreadCalls = false` is a very bad idea! For the exception: check the stack trace exactly, `f` cannot be null, or see: https://stackoverflow.com/questions/4660142/what-is-a-nullreferenceexception-and-how-do-i-fix-it – René Vogt May 18 '18 at 12:05
  • 1
    You need to learn about multi-threaded programming and the approaches available before you just jump blindly into it. It's an advanced topic to put it mildly, and if you don't understand what you're doing, you'll have endless issues and unhappy customers. – rory.ap May 18 '18 at 12:06
  • @EvanBarke you *can't* modify the UI from another thread, in any OS. This means you *can't* modify the grid from `DoWork`. You have to handle the `RunWorkerCompleted` event and modify the UI there. It's a lot easier to use `async/await` by this point. Combining two async operations with BGW is a lot harder too – Panagiotis Kanavos May 18 '18 at 12:13
  • Alright thanks guys. I figured I was doing something a bit wrong when hacking this together. I was trying to use the ReportProgress() method of the worker but it's more complex than just an integer of progress. Each row in the datagrid has it's own progress bar. So which approach should I take? – Evan Barke May 18 '18 at 12:27

2 Answers2

0

You should use the BackgroundWorker.ReportProgress Method (Int32, Object) as shown in the documentation here. I always use the second parameter to pass an enum to identify from which background worker was this progress reported. You can use a similar approach.

Soham Dasgupta
  • 4,675
  • 20
  • 70
  • 120
  • Thanks but this doesn't help. ReportProgress reports an percentage value. I need to replace the entire datasource for the dataGridView1 – Evan Barke May 18 '18 at 13:30
  • You can pass your datatable as the second parameter and you actually don't need to care about the percentage. – Soham Dasgupta May 18 '18 at 13:47
0

So I found a solution that works nicely. Basically I changed the background worker to a task that gets the data and then invokes the dataGridView that needed updating. E.g.

Task.Factory.StartNew(() =>
                    UpdateUI()); 

Renamed the background worker to UpdateUI and inside:

var dw = DwData(_dwConnectionString);
            var source = SourceData(_sourceConnectionString);
            DataTable prog;
            while (isLoading)
            {
                System.Threading.Thread.Sleep(1000);
                dw = DwData(_dwConnectionString);
                if (dw.Rows.Count > 0)
                {                     
                    prog = GetProgress(dw, source);
                    if (prog.Rows.Count > 0)
                    {
                        dataGridView1.Invoke(new MethodInvoker(() => { dataGridView1.DataSource = prog; dataGridView1.Refresh(); }));
                    }
                }

            }
Evan Barke
  • 89
  • 1
  • 10