-2

In c# winforms application I have datagridview with two dates start date and end date. I need to fill datagridview within the range of date. It takes a lot of time to fill the datagridview and windows form exe becomes unresponsive. I am now using background worker. It fills datagridview when I click search button after selecting date. I click the clear button to empty the datagridview. when i select date again and click search button I get the error.

cross-thread operation not valid Control accessed from a thread other than thread it was created on.

  private clsStkBAL obj = new clsStkBAL();

       
        public frmSalesStk()
        {
            InitializeComponent();
            backgroundWorker1.WorkerReportsProgress = true;

        }
       
        private void FillGrid()
        {
            try
            {

                this.obj.SP_STATUS = nameof(FillGrid);//SP_SalesInvoice
                this.obj.Sih_StartDate = new DateTime?(this.dtpFromdt.Value.Date);//set_Sih_StartDate
                this.obj.Sih_EndDate = new DateTime?(this.dtpToDate.Value.Date);
                clsCommon.BindeDataGridView(this.dgvStock, (object)this.obj.GetDataTable((clsSalesStock)this.obj, (SqlTransaction)null, false), false);
                // ExcelPackage.LicenseContext = LicenseContext.NonCommercial;
            }

            catch (Exception ex)
            {
                clsCommon.ShowErrorMessage(ex.Message);
            }
        }

        private void btnSearch_Click(object sender, EventArgs e)
        {
           if(backgroundWorker1.IsBusy)
            {
                clear();
            }       
                backgroundWorker1.RunWorkerAsync(2000);
           
            
        }


      
        
        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {

            if (btnSearch.InvokeRequired)
            {

                dgvStock.Invoke(new Action(FillGrid));

            }
            FillGrid();
          
          
        }
        private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            if (backgroundWorker1.IsBusy)
            {
                Thread.Sleep(20000);
             
            }
        }

        private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
          
            MessageBox.Show("Loaded");
            backgroundWorker1.Dispose();
            dtpFromdt.Enabled = true;
            dtpToDate.Enabled = true;
        }

        private void frmSalesStock_Load(object sender, EventArgs e)
        {
            dtpFromdt.Value = new DateTime(2021, 02, 01);
            dtpToDate.Value = new DateTime(2021, 02, 01);

            backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
        }
        public void clear()
        {

            dgvStock.DataSource = null;
            backgroundWorker1.CancelAsync();
        }
        private void btnClear1_Click(object sender, EventArgs e)
        {
            clear();
        }
    }
how to fix this error.pls

how to fix this error.

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
gok
  • 1
  • 1
  • You seem to have posted more code than what would be reasonable for your issue. Please read [ask] and how to make a [mre]; providing a MRE helps users answer your question and future users relate to your issue. – gunr2171 Mar 03 '23 at 04:55
  • The whole point of using a `BackgroundWorker` is so you don't have to call `Invoke`. What you should be doing is retrieving the data and populating a `DataTable` in the `DoWork` event handler and then binding that `DataTable` to your grid in the `RunWorkerCompleted` event handler. You really need to spend some time to learn how to use a `BackgroundWorker` properly. [Here](https://www.vbforums.com/showthread.php?471889) is my take on that. – jmcilhinney Mar 03 '23 at 05:27

2 Answers2

0

This line looks dodgy to me:

dgvStock.Invoke(new Action(FillGrid));

The error is because you're supposed to handle UI things on the main thread:

cross-thread operation not valid Control accessed from a thread other than thread it was created on.

It appears you're invoking an Action in a Background worker and that has problems - do it off the main thread - here's a an example I wrote, you can use a CodeConverter to port it from VB.Net back to C#:

https://stackoverflow.com/a/13486676/495455

Jeremy Thompson
  • 61,933
  • 36
  • 195
  • 321
0

There are two threads involved: the UI-thread (the "main" thread), and your BackGroundWorker. Only the UI-thread is allowed to access any of the visual items. So only the UI-thread may fetch text from text boxes, only the UI-thread may show or hide labels, or redraw forms. Whenever any other thread tries to access a Control, you get the exception that you described.

So: how to make sure that the UI-thread will update the data that the BackgroundWorker has created?

Answer: send this data to the UI-thread using events ProgressChanged and RunWorkerCompleted. Both events allow data to be sent to the UI-thread.

You have to ask yourself: must the datagridview be updated while the BackgroundWorker is still busy, or is it good enough that the operator sees that the BackgroundWorker is progressing?

You chose a separate thread to do the lengthy calculations, in order to keep your UI responsive. So you want to allow the operator to do something with the form, while you are doing the calculations.

But what if the operator changes the DataGridView during these calculations? Or maybe not change, but only selects some cells to copy-paste to something else? Won't your constantly updating the DataGridView interfere with this?

You're right, I only want to show progress, and update when all data if calculated.

In that case event ProgressChanged will only show that some progress has been made. Event RunWorkerCompleted will contain all calculated data that must be updated in the DataGridView.

No! whenever the backgroundworker has calculated some data, I want to show it as soon as possible!

In that case: attach the new data to ProgressChanged event.

In your form:

void StartBackGroundCalculations(...)
{
    // this method is run by the UI-thread!

    if (this.backgroundWorker1.IsBusy)
    {
        // background calculations already busy. Decide what you want to do:
        // show message to operator?
        // restart the calculations?
        // exception?
        this.ProcessBusyBackgroundWorker();
        return;
    }

    // if you want to show progress, for instance in a ProgressBar
    // initiate it now:
    this.IntiateProgressDisplay();

    // start the background work
    // if needed, transfer data from your form to the background worker,
    // so the backgroundworker doesn't have to access your text boxes, datagridview etc
    MyBackGroundWorkerData startData = this.CreateBackgroundStartData() // todo: implement
    this.backgroundWorker1.RunWorkerAsync(startData);
}

Event DoWork: run by the backgroundWorker, so no access to any UI element.

Three versions:

  • don't show any progress, only show the end result
  • show Progress in a Progress bar and the end result
  • show the actual progress data as soon as it is calculated

(1) No progress at all:

void OnBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
    // run by BackgroundWorker, no UI-access allowed
    MyBackGroundWorkerData startData = (MyBackGroundWorkerData)e.Argument;
    MyEndResult endResult = this.CalculateTotalData(startData);

    // report end result to UI-thread
    e.Result = endResult;
}

(2) Show simple Progress

void OnBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker backgroundWorker = (BackgroundWorker)sender;

    // run by BackgroundWorker, no UI-access allowed
    MyBackGroundWorkerData startData = (MyBackGroundWorkerData)e.Argument;
    
    List<MyData> endResult = new List<MyData>()
    
    int progressPercentage = 0;
    MyData intermediateResult = this.CalculateIntermediateResult();
    // method returns null if there is nothing to calculate anymore
    // expect no more than 50 calculations
    while (intermediateResult != null)
    {
        endResult.Add(intermediateResult);
        progressPercentage += 2;

        // report progress:
        backgroundWorker.ReportProgress(progressPercentage, null);
        // null: no extra data in the progress report
    }

    // if here: all data calculated; report end result to UI-thread
    backgroundWorker.ReportProgress(100, null);   // report progress: 100%
    e.Result = endResult;
}

(3) Show progress data

void OnBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker backgroundWorker = (BackgroundWorker)sender;

    // run by BackgroundWorker, no UI-access allowed
    MyBackGroundWorkerData startData = (MyBackGroundWorkerData)e.Argument;
    
    List<MyData> endResult = new List<MyData>()
    
    int progressPercentage = 0;
    MyData intermediateResult = this.CalculateIntermediateResult();
    // method returns null if there is nothing to calculate anymore
    // expect no more than 50 calculations
    while (intermediateResult != null)
    {
        endResult.Add(intermediateResult);
        progressPercentage += 2;

        // report progress, include intermediateResult
        backgroundWorker.ReportProgress(progressPercentage, intermediateResult);
    }

    // if here: all data calculated; report end result to UI-thread
    backgroundWorker.ReportProgress(100, null);   // report progress: 100%
    e.Result = endResult;
}

Sometimes it is very difficult to give a percentage as progress. In that case, don't use this field, try to invent a different method to tell the operator that the background work is really progressing.

The event progress changed is handled by the UI-thread: this thread is allowed to access all UI elements, including progress bar and datagridview

void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
    // if you agreed that the percentage is filled, update your progress display
    this.UpdateProgressDisplay(e.ProgressPercentage);

    // if you agreed to communicate actual progress data
    MyData intermediateResult = (IntermediateResult)e.UserState;
    this.ProcessProgress(intermediateResult);
    // this will do what you want to do with the intermediate data,
    // for instance update the DataGridView
}

And the result if the Background worker reports complete. This is run by the UI-thread, so you can access the DataGridView and the progress bar, or whatever method you use to show the progress

private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    // check if Error / Cancelled
    if (e.Error) ... // TODO: decide what to do if error
    if (e.Cancelled) ... // TODO: decide what to do if cancelled

    // depending on what you agreed as the end result data:
    List<MyData> endResult = (List<MyData>)e.Result;
    this.UpdateEndResult(endResult); // updates the datagridview

    // if you had some Progress display, like a progress bar, hid it now
    this.HideProgressDisplay();
}

Conclusion

The event handler of event DoWork is run by the BackgroundWorker: it may not touch any user interface elements. If it needs data, use the eventargs to send it data Use e.Result to fill in the end result

The event handers of events ProgressChanged and RunWorkerCompleted are run by the UI. These procedures can access the UI elements

Harald Coppoolse
  • 28,834
  • 7
  • 67
  • 116