Hello,
I've spoken with Infragistics Support about this issue (support request CAS-08857-GUP6P0 if Infragistics engineers (for instance Mike :) ) would like to take a look at it). Unfortunately, the answer was pretty much depressing. Our current implementation looks like this:
We have a form with a grid on it. There is a System.Data.DataSet bound to the grid. To be more specific, let’s assume that the user enters some article ids (one in each row). After the user is done with ids, he presses a button which starts some long running calculation (computation of optimal prices). Currently we do the following:
1. We call BeginUpdate() and SuspendRowSynchronization() on the grid. 2. We start the thread which changes the dataset 3. At the very end this method (which changes the dataset) calls UpdateGrid()
4. private void UpdateGrid()
{
if(InvokeRequired)
Invoke(new MethodInvoker(UpdateGrid));
}
else
ultraGrid1.ResumeRowSynchronization();
ultraGrid1.EndUpdate();
So, we do everything in our power to prevent access to UI controls (grid) from a non UI thread.
According to Infragistics Support the described implementation still doesn’t guarantee that the grid won’t “try to query the datasource for changes. If a property is set on the grid, or an action is taken, such as moving the mouse, then the grid will try to talk to its data source to retrieve the values and this is where the problems occur”.
So, my question is whether there is some solution for this more than usual problem (without unbinding the grid completely from the datasource like grid.DataSource=null). Would setting AllowUpdate=false help? Would grid.Enabled = false help?
I’m more than aware that I need to “synchronize worker threads with the UI”. My task is to keep the UltraGridRows and Layout (therefore I don’t want to unbind the DataGrid from the DataSet) AND perform some long running operation which changes the dataset on a separate thread. I would very appreciate an algorithm in pseudo code.
There's really no way to do this, since you cannot control when the grid might try to access the data.
What you would have to do is have your data source on the second thread deal with some other object on the UI Thread which is then bound to the grid. This data source would have to be something over which you have complete control, so you could make sure it deals with the other thread properly at all times.
The code you have here is actually not bad. Assuming that this is the only place you are using the second thread, this might work. But... if you are starting the thread in step 2, and you are calling the UpdateGrid method "at the very end of this method", what ensures that the thread has finished when you are calling ResumeRowSynchronization on the grid? Unless you left out some pretty big steps here, you appear to be assuming that your thread is working synchronously and is therefore complete and soon as the next line of code is executed.
Hello Mike,
Code says more than thousand words :)
--------------------------------
private void Form1_Load(object sender, EventArgs e) { ultraGrid1.SetDataBinding(CreateHierarchicalDataSet(100, 100), "parent"); }
private void ultraButton1_Click(object sender, EventArgs e) { ultraGrid1.BeginUpdate(); ultraGrid1.SuspendRowSynchronization(); //Problem with BeginUpdate(). Uncomment this line an trying moving this new window //on the screen while ultraGrid is between BeginUpdate() and EndUpdate() //The ultragrid visual appearance is then broken (it's better to see it) //Commenting out Begin/EndUpdate() makes datagrid to react to changes in the datasource again //Why is SuspendRowSynchronization() not enough? //new Form().Show(); ThreadPool.QueueUserWorkItem(ChangeDataSet); } private void ChangeDataSet(object state) { int x = 100; while (x > 0) { foreach (DataRow row in dataSet.Tables[0].Rows) { row["DateTime"] = ((DateTime)row["DateTime"]).AddDays(1); } x--; } UpdateGrid(); } private void UpdateGrid() { if(InvokeRequired) { Invoke(new MethodInvoker(UpdateGrid)); } else { ultraGrid1.ResumeRowSynchronization(); ultraGrid1.EndUpdate(); MessageBox.Show("Changing data set done"); } }
So, as you can see, we call ResumeRowSynchronization() and EndUpdate() from the worker thread (when operations on the dataset are really done) AND make necessary synchronization with the UI thread (with Invoke()). I also attach a sample if you would like to try it out.
Is this approach safe? Or it is still not allowed to use a dataset as a datasource and we need to replace it with our own object which must synchronize the access and prevent the grid to touch the datasource when it is being changed from a worker thread?
You seem to have covered a lot of the bases here, but I still don't think this is 100% safe. SuspendRowSynchronization is designed to prevent the grid from responding to certain notifications from the data source, but it was not designed with threading in mind, and it may not handle ALL notifications.
Even if it did, there are still notifications coming from the BindingManager. And speaking of the BindingManager, there's nothing that says the BindingManager itself is thread-safe as far as I know.
But even if the grid completely stopped responding to all notifications from the BindingManager and the data source, this would still not be safe, because it's entirely possible for the grid to attempt to access the data source in response to something else like an invalidation or when the mouse moves over a grid cell and the data will be in a bad state at that point. Or if the user does something else in your application - like suppose the user clicks on a button that attempts to access the grid in some way while the background thread is running.
Actually what I need is the way to tell the grid: “DataSource is busy right now. Don’t listen to events coming from the datasource (inclusive BindingManager and similar sources which actually mean “datasource”), because these events couldn’t be suppressed and may harm you. Don’t touch the datasource itself. Either use the values you already have anyway (which are shown in the cells) or throw an exception if access to datasource was explicitly demanded after it was said that datasource is busy”.
I don’t think that we’re asking for something exotic here. I would rather say that the suggestions “make a proxy object between grid and dataset and use proxy as datasource” etc. are very clumsy.
Of course, if such functionality is not implemented in the grid, we need to find another solution. But I honestly don’t understand how Infragistics could miss such a huge nugget of functionality and how other applications which use NetAdvantage for WinForms can work. Do they let the application hang each time some operation is started which takes more than 5 seconds?
Andrej Kuklin said:Actually what I need is the way to tell the grid: “DataSource is busy right now. Don’t listen to events coming from the datasource (inclusive BindingManager and similar sources which actually mean “datasource”), because these events couldn’t be suppressed and may harm you. Don’t touch the datasource itself. Either use the values you already have anyway (which are shown in the cells) or throw an exception if access to datasource was explicitly demanded after it was said that datasource is busy”.
Yes, I agree that that is what you would need. But there is no such property, and it's not at all a simple or trivial thing to do. For one thing, the grid does not copy your entire data source into it's own memory. That would be horribly inefficient. Any time a grid cell paints, the grid gets the value of that cell from the data source. So right there would be a huge problem and that's just one of many. So even if such a property or method were added to the grid, while it was in such a state, the grid would be unable to function in any meaningful way, or even paint itself.
Andrej Kuklin said:Of course, if such functionality is not implemented in the grid, we need to find another solution. But I honestly don’t understand how Infragistics could miss such a huge nugget of functionality and how other applications which use NetAdvantage for WinForms can work. Do they let the application hang each time some operation is started which takes more than 5 seconds?
If the operation is synchronous, then yes, I beleive that is what they do. I know of no controls, either third-party or from Microsoft, that make a claim to being thread-safe.
Even if the grid had a property as you suggest, and even it was completely thread-safe, the DotNet BindingManager/CurrencyManager is not. So it would continue to update it's own state and could easily get out of snych.
We currently do
UltraGrid.SuspendRowSynchronization();
UltraGrid.DisplayLayout.Override.AllowUpdate = DefaultableBoolean.False;
view.Enabled = false;
AND (drums!) hide the grid replacing it with a screenshot. This works pretty stable, no issues in last 1,5 years, but of course behaves awfully when the user resizes the form (fortunatelly, very rarely in our application). At least the user can use other functionality
Control control = view.UltraGrid; // Hack: Replace grid by screenshot and make grid invisible to prevent drawing events. Bitmap bitmap = new Bitmap(control.Width, control.Height); control.DrawToBitmap(bitmap, Rectangle.FromLTRB(0, 0, bitmap.Width, bitmap.Height)); view.DisabledGridPictureBox.Image = bitmap; view.DisabledGridPictureBox.Dock = DockStyle.Fill; view.DisabledGridPictureBox.SizeMode = PictureBoxSizeMode.Normal; view.UltraGrid.Visible = false; view.DisabledGridPictureBox.BringToFront();
Did anyone find any solution to this problem, I am using version 10 of infragistics libraries.
thanks
Thank you, Mike, for detailed explanation. I will think about possible alternatives.