views:

60

answers:

1

I have a GridControl which I populate using a BackgroundWorker. Then I'm using another BackgroundWorker to perform some calculations on the dataset which is the datasource of the GridControl. As I'm trying to do this a cross thread operation on the GridControl error is thrown. I'm unable to understand that despite not performaing any operation on the gridcontrol itself how the error is generating. (I'm using DevExpress, but that should not change the concept).

Also is there any way I can use one BackgroundWorker to do different work, i.e. make this code more efficient.

Here is my code:-

public partial class MainForm : XtraForm
    {
        private BackgroundWorker loadworker = new BackgroundWorker();
        private BackgroundWorker calcworker = new BackgroundWorker();
        private AutoResetEvent resetEvent = new AutoResetEvent(false);
        private Database _db = EnterpriseLibraryContainer.Current.GetInstance<Database>("ConnString");
        private DataSet ds;

        public MainForm()
        {
            InitializeComponent();

            loadworker.DoWork += loadworker_DoWork;
            loadworker.RunWorkerCompleted += loadworker_RunWorkerCompleted;
            loadworker.ProgressChanged += loadworker_ProgressChanged;
            loadworker.WorkerReportsProgress = true;

            calcworker.DoWork += calcworker_DoWork;
            calcworker.RunWorkerCompleted += calcworker_RunWorkerCompleted;
            calcworker.ProgressChanged += calcworker_ProgressChanged;
            calcworker.WorkerReportsProgress = true;
        }

        private void calcworker_DoWork(object sender, DoWorkEventArgs e)
        {
            int _cnt = 0;
            foreach (DataRow dr in ds.Tables[0].Rows)
            {
                dr["GROSS"] = (decimal)dr["BASIC"] + (decimal)dr["HRA"] + (decimal)dr["DA"];
                _cnt += 1;
            }

            for (int i = 0; i <= _cnt; i++)
            {
                Thread.Sleep(100);
                calcworker.ReportProgress((100 * i) / _cnt);
            }
        }

        private void calcworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.SetState(true);
            this.MainInit();
        }

        private void calcworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.pgb_DataProgress.Position = e.ProgressPercentage;
        }


        private void loadworker_ProgressChanged(object sender, ProgressChangedEventArgs e)
        {
            this.pgb_DataProgress.Position = e.ProgressPercentage;
        }

        private void loadworker_DoWork(object sender, DoWorkEventArgs e)
        {
            try
            {
                DbCommand _cmd = _db.GetSqlStringCommand("SELECT Z.EMP_CODE,Z.BASIC,Z.DA,Z.HRA,CAST(0 AS DECIMAL) GROSS FROM Z000000001 Z");
                DataSet _data = _db.ExecuteDataSet(_cmd);

                for (int i = 0; i <= 10; i++)
                {
                    Thread.Sleep(500);
                    loadworker.ReportProgress((100 * i) / 10);
                }

                e.Result = _data;
            }
            catch (Exception ex)
            {
                e.Cancel = true;
            }
        }

        private void loadworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
        {
            this.ds = (DataSet)e.Result;
            this.gridControl1.DataSource = ds.Tables[0];
            this.SetState(true);
            this.MainInit();
        }

        private void btn_FetchData_Click(object sender, EventArgs e)
        {
            this.gridControl1.DataSource = null;
            this.SetState(false);
            loadworker.RunWorkerAsync();
        }

        private void SetState(bool _state)
        {
            this.btn_Calculate.Enabled = _state;
            this.btn_ClearGrid.Enabled = _state;
            this.btn_FetchData.Enabled = _state;
        }

        private void MainInit()
        {
            this.pgb_DataProgress.Position = 0;
        }

        private void btn_ClearGrid_Click(object sender, EventArgs e)
        {
            this.gridControl1.DataSource = null;
        }

        private void btn_Calculate_Click(object sender, EventArgs e)
        {
            if (this.gridControl1.DataSource == null)
            {
                DevExpress.XtraEditors.XtraMessageBox.Show("Data Not loaded", "Message");
                return;
            }
            else
            {
                this.SetState(false);
                calcworker.RunWorkerAsync();
            }
        }

    }
A: 

In short, you cannot access controls on a thread other than UI thread on which they are created. So any control method/property call has to be marshall on the UI thread using Control.Invoke method.

For example, in your case loadworker_RunWorkerCompleted event handler will be invoked on a worker thread and accessing control property will throw an error. You need to modify event handler as

    private void loadworker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
    {
        System.Action a = () => {
          this.ds = (DataSet)e.Result;
          this.gridControl1.DataSource = ds.Tables[0];
          this.SetState(true);
          this.MainInit();
        };
        this.gridControl1.Invoke(a);
    }
VinayC
@VinayC: What is the best way to tackle this problem.
Soham Dasgupta
@Soham, edited to show how to use invoke when doing cross thread operation. You have to use such code at every method accessing controls that may get invoked on different thread.
VinayC
-1, This is wrong! The Completed event is executed on the main GUI Thread so this Invoke stuff is completely unneeded. And certainly not solving the problem.
Henk Holterman
@VinayC: Works like a charm.. Can you explain the System.Action? Is it lambda function?
Soham Dasgupta
@Soham, this worked? You must have changed something else too.
Henk Holterman
@Soham - System.Action is a helper delegate type and I have created its instance using a lambda expression. Control.Invoke will not accept lambda (as actual delegate type cannot be inferred).
VinayC
@Henk, what you are saying is right only if Soham had created BackgroundWorker on form design surface - only is such case, those worker would know about parent GUI context to marshal completed event to GUI thread. But in Soham's case, he is creating workers in code and they are not associated with the form anyway hence the need for marshalling.
VinayC
@VinayC: Can I write a Action delegate without using lambda expression? Can you give me an example?
Soham Dasgupta
@VinayC: Incorrect, the Bgw will find the right thread using its ExecutionContext. It is easy to try out. Completed and ProcessChanged always happen on the GUI thread
Henk Holterman
@Henk: I wrote it inside the DoWork event. It worked. But how would I make it work in .NET 2.0. I think this is not available in .NET 2.0 is it?
Soham Dasgupta
@Soham, you can use Anonymous Methods syntax.In .NET 2.0, you have to declare some delegate such as public delegate void MyAction(); And then MyAction a = delegate() { ... };
VinayC
@VinayC: Thanks for the info.
Soham Dasgupta
@Henk - thanks for correcting. Checking from reflector, BackgroundWorker indeed adds logic for invoke while adding event handler. However, it still beats me that Soham's problem has been resolved because DoWork does not seem to be accessing any control.
VinayC
@VinayC: It beats me too.
Henk Holterman
@VinayC: I don't know whether DevExpress gridControl has been rigged to do so.
Soham Dasgupta