Does it mean that Invoke() method has to be called on each UI control?

耗尽温柔 提交于 2020-01-06 13:27:07

问题


Following is a method that does Insert into the database table. And I am calling this method within DoWork() of BackGroundWorker thread. It obviously throws me the "cross thread operation not valid..." error. As I understand, I could use Invoke() method on UI controls if those to be accessed within DoWork(). BUT does it mean each of the following UI controls should have to be invoked? Is there a better way to achieve this?

    private void AddToOccupations()
    {
        if (dataGridViewSearch.SelectedRows.Count > 0)
        {
            foreach (DataGridViewRow datarow in dataGridViewSearch.SelectedRows)
            {
                try
                {
                    AllocationModel occupation = new AllocationModel()
                    {
                        User_ID = userID,
                        Merge_Status = (int)((MergeStatus)Enum.Parse(typeof(MergeStatus), cmbMergeStatus.SelectedItem.ToString())),
                        Start_Date = dateTimePickerFROMDate.Value,
                        Seat_Type = datarow.Cells[2].Value.ToString(),
                        Occupation_Status = cmbStatus_Type.SelectedItem.ToString(),
                        Session = datarow.Cells[3].Value.ToString(),
                        Seat_Number = (Int32)datarow.Cells[0].Value,
                        Number_of_Guests = (Int32)datarow.Cells[1].Value
                    };

                    // Call the service method 
                    var success = this.allocationService.AddToOccupation(occupation);

                    if (success)
                    {
                        // display the message box
                        MessageBox.Show(
                            Resources.Add_Occupation_Success_Message,
                            Resources.Add_Occupation_Success_Title,
                            MessageBoxButtons.OK,
                            MessageBoxIcon.Information);

                        // Reset the screen
                        //this.Reset();

                        DoCurrentFreeSearch();
                    }
                    else
                        MessageBox.Show("No Records Inserted!");
                }
                catch (FormatException ex)
                {
                    errorMessage = "Insert Error: \n";
                    errorMessage += ex.Message;
                    MessageBox.Show(errorMessage);
                }
            }
        }
        else
            MessageBox.Show("Warning! No Row is selected...");
    }

回答1:


The general answer to your title question is: Yes.

If a background worker thread needs to access a UI item it can only do so by Invoking a method of it.

The problem of putting data into a DGV from a BW is best addressed by accessing its DataSource in a de-coupled way. I just did a little test to see if my suggestion from the original post works and it looks fine.

So, like with the Bitmap, you can create two DataSources as Properties; one to fill from the background worker and one to use as the DGV's Datasource.

public List<SeatData> theSeats_buffer { get; set; }
public List<SeatData> theSeats_DS { get; set; }

In the BW thread DoWork() you fill the theSeats_buffer list by calling an appropriate function void or bool getSeatData() and when you are done with the workload you pass the data into the theSeats_DS, maybe like this:

theSeats_DS= new List<Cols>(); theSeats_DS.AddRange(theSeats_buffer);

Again, this operation must be made thread-safe, probably by locking the receiving list theSeats_DS.

since the DataSource has been re-created it should be re-assigned in the bw_RunWorkerCompleted event; I did it right along with invalidating the display panel1:

private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    if ((e.Cancelled == true))
    { this.tbProgress.Text += "Cancelled!";    }
    else if (!(e.Error == null))
    {  this.tbProgress.Text += ("Error: " + e.Error.Message);    }
    else
    { panel1.Invalidate(); DGV.DataSource = theSeats_DS; }
}

With regard to the DB Inserts, I don't know how it relates. This answer is only about getting data from somewhere asynchrously and sending them to the UI.

Getting data from the DGV into the database is not something that would happen in the BW thread though, at least not the one that gets triggered upon incoming changes. If you use the DGV for input, concurrency will be an issue!! It is bad enough, if the seat one tries to reserve is actually taken by the time you press enter. But that can't be prevented. However, you need to ensure that the input isn't wiped out by incoming changes.. OTOH, a signal would be nice..

Concurrency is tricky!




回答2:


You should separate the worker code from the GUI code. Don't you have a DataSource on that data grid? You should basically first get the data you need from the grid (the selected rows) and pass them to the background worker from the GUI code (the button click or whatever). You can then report the work progress through BackgroundWorker.ReportProgress (which executes the progress event on the GUI thread).

If you can't decouple the GUI code from the worker code completely, you'll have to use Invokes for the rest. However, it's not clear from your code why you would need that, ReportProgress should be quite enough.




回答3:


Since the controls run on the UI thread, and not on the background worker thread, all of their functions need to be invoked.



来源:https://stackoverflow.com/questions/24510531/does-it-mean-that-invoke-method-has-to-be-called-on-each-ui-control

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!