Работа с сеткой с помощью BackgroundWorker

У меня есть GridControl, который я заполняю с помощью BackgroundWorker. Затем я использую другой BackgroundWorker для выполнения некоторых вычислений с набором данных, который является источником данных GridControl. Когда я пытаюсь сделать это, возникает перекрестная операция с ошибкой GridControl. Я не могу понять, что, несмотря на то, что я не выполняю никаких операций с самим gridcontrol, как генерируется ошибка. (Я использую DevExpress, но это не должно менять концепцию).

Также есть ли способ использовать один BackgroundWorker для выполнения другой работы, то есть сделать этот код более эффективным.

Вот мой код: -

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();
            }
        }

    }

person Soham Dasgupta    schedule 23.09.2010    source источник
comment
Вы изменяете DataRow изнутри calcworker_DoWork. Через уведомление об изменении ваша сетка будет проинформирована, и это делается из того же потока, что и изменение строки. Вот почему код выдает исключение. Возможно было бы установить строку в loadworker_ProgressChanged. Вы можете передавать данные через UserState метода ReportProgress.   -  person HCL    schedule 23.09.2010
comment
@HCL: Итак, каким должен быть подход. Должен ли я сначала установить источник данных GridControl равным нулю, затем выполнить вычисления, а затем снова установить источник данных. Этот метод работает, но это хороший способ или вы предлагаете иное.   -  person Soham Dasgupta    schedule 23.09.2010
comment
То, как вы говорите, является самым быстрым способом, потому что между потоками связи не происходит. Однако пользовательский опыт может быть не таким хорошим, если это долгосрочная операция. Можно использовать Control.Invoke, как написал VinayC, или показать индикатор выполнения, или вы устанавливаете данные в строке через событие ProgressChanged.   -  person HCL    schedule 23.09.2010


Ответы (2)


Короче говоря, вы не можете получить доступ к элементам управления в потоке, отличном от потока пользовательского интерфейса, в котором они созданы. Таким образом, любой вызов метода/свойства управления должен быть организован в потоке пользовательского интерфейса с помощью Control. Вызвать метод.

Например, в вашем случае обработчик событий loadworker_RunWorkerCompleted будет вызываться в рабочем потоке, и доступ к управляющему свойству вызовет ошибку. Вам нужно изменить обработчик событий как

    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);
    }
person VinayC    schedule 23.09.2010
comment
@VinayC: Как лучше всего решить эту проблему. - person Soham Dasgupta; 23.09.2010
comment
@Soham, отредактировано, чтобы показать, как использовать вызов при выполнении операции с несколькими потоками. Вы должны использовать такой код в каждом методе доступа к элементам управления, которые могут быть вызваны в другом потоке. - person VinayC; 23.09.2010
comment
-1, Это неправильно! Событие Completed выполняется в основном потоке GUI, поэтому этот материал Invoke совершенно не нужен. И уж точно не решение проблемы. - person Henk Holterman; 23.09.2010
comment
@VinayC: работает как шарм .. Можете ли вы объяснить System.Action? Это лямбда-функция? - person Soham Dasgupta; 23.09.2010
comment
@Сохам, это сработало? Вы, должно быть, изменили что-то еще. - person Henk Holterman; 23.09.2010
comment
@Soham - System.Action — это вспомогательный тип делегата, и я создал его экземпляр с помощью лямбда-выражения. Control.Invoke не будет принимать лямбда (поскольку фактический тип делегата не может быть выведен). - person VinayC; 23.09.2010
comment
@Хенк, то, что вы говорите, верно только в том случае, если Сохам создал BackgroundWorker на поверхности дизайна формы - только в этом случае этот работник будет знать о родительском контексте графического интерфейса, чтобы маршалировать завершенное событие в поток графического интерфейса. Но в случае с Сохамом он создает воркеров в коде, и они в любом случае не связаны с формой, отсюда и необходимость сортировки. - person VinayC; 23.09.2010
comment
@VinayC: Могу ли я написать делегат Action без использования лямбда-выражения? Можете ли вы привести пример? - person Soham Dasgupta; 23.09.2010
comment
@VinayC: неверно, Bgw найдет правильный поток, используя свой ExecutionContext. Это легко попробовать. Completed и ProcessChanged всегда происходят в потоке графического интерфейса. - person Henk Holterman; 23.09.2010
comment
@Henk: я написал это на мероприятии DoWork. Это сработало. Но как мне заставить его работать в .NET 2.0. Я думаю, что это недоступно в .NET 2.0, не так ли? - person Soham Dasgupta; 23.09.2010
comment
@Soham, вы можете использовать синтаксис анонимных методов. В .NET 2.0 вам нужно объявить некоторый делегат, например открытый делегат void MyAction(); И затем MyAction a = делегат() { ... }; - person VinayC; 23.09.2010
comment
@VinayC: Спасибо за информацию. - person Soham Dasgupta; 23.09.2010
comment
@Хенк - спасибо за исправление. Проверяя из рефлектора, BackgroundWorker действительно добавляет логику для вызова при добавлении обработчика событий. Тем не менее, меня все еще удивляет, что проблема Сохама была решена, потому что DoWork, похоже, не имеет доступа к какому-либо управлению. - person VinayC; 23.09.2010
comment
@VinayC: я не знаю, был ли DevExpress gridControl настроен для этого. - person Soham Dasgupta; 23.09.2010

После того, как вы прикрепили таблицу как источник данных, она принадлежит графическому интерфейсу. Предположим, ваш пользователь изменяет/удаляет строку во время работы вашего потока Calc. Возможны всевозможные условия гонки.

person Henk Holterman    schedule 23.09.2010