DataGridView memory problem

Posts   
 
    
Posts: 93
Joined: 13-Feb-2008
# Posted on: 04-Apr-2008 15:17:54   

So the scenario is:

  1. Load 8k entities into an entitycollection (once)
  2. Bind the datagridview via a binding source to an entityview2 based on the entitycollection
  3. The initial entityview2.Filter results in 0 matches for the entityview2

At this point all the entities are loaded into memory (just not shown) and the memory footprint is about 165,000K (per task manager)

  1. Click a radio button that does nothing more than set the Filter property on the enityview2 which triggers a list changed event (see llblgen source) and via databinding the datagridview is refreshed with about 4k rows.

At this point the memory footprint jumps about 20,000K (per task manager)

  1. Click another radio button which resets the Filter property of the entityview which results in 100 visible rows.

At this point the memory jumps a modest amount

  1. Click the first radio button resulting in 4k rows in the datagridview.

At this point the memory jumps another 20,000K (per task manager). These memory jumps are never being reclaimed by the GC.

So by simply toggling between the 2 radio buttons and thus changing the Filter property on the entityview2 I can crash the app in about 10 toggles because the datagridview keeps sucking up more and more memory. I know the entityview2 objects aren't the culprit because I ran a test with no gui toggling the filter about 50 times with no significant memory gain.

Does anyone know what the datagridview is doing internally? Obviously it keeps recreating some internal list and not releasing the references. Anyone know a work around for this behavior?

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 04-Apr-2008 15:55:54   

This is a VS 2008, right? can you reproduce it using VS 2005?

Would you please post code snippets? Or better attach a simple repro solution (better be based on northwind)?

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 04-Apr-2008 17:41:19   

It's likely an event thing, where some event is bound but not released (so objects are kept in memory).

You could try when you switch the view, to first set the DataSource of the grid to null. Then switch the view's filter, then rebind.

(I find 165MB of memory for 8000 entities pretty steep as well, are they containing big datablocks?)

Frans Bouma | Lead developer LLBLGen Pro
Posts: 93
Joined: 13-Feb-2008
# Posted on: 04-Apr-2008 21:00:49   

I think I have it isolated and its blowing my mind...

I call the following method after resetting the filter on the entityview2. If I comment out this method body I see no appreciable memory consumption, if I leave it in the app grows by 20MB every time I change the entityview2 filter. This seems like a very innocent piece of processing, does anyone have any insight as to what could be happening here?



private void CalculateTotal()
        {
            DataGridViewRowCollection allRows = MainDataGridView.Rows;
            if (allRows.Count > 0)
            {
                decimal totalCurrency = 0;
                PaymentRequestEntity pre;

                foreach (DataGridViewRow dgvr in allRows)
                {
                    pre = dgvr.DataBoundItem as PaymentRequestEntity;
                    totalCurrency += pre.EstimatedPayerAmount;
                }

                lblTotal.Text = totalCurrency.ToString();
            }
        }


simmotech
User
Posts: 1024
Joined: 01-Feb-2006
# Posted on: 06-Apr-2008 06:35:12   

becker-samm wrote:

I think I have it isolated and its blowing my mind...

I call the following method after resetting the filter on the entityview2. If I comment out this method body I see no appreciable memory consumption, if I leave it in the app grows by 20MB every time I change the entityview2 filter. This seems like a very innocent piece of processing, does anyone have any insight as to what could be happening here?



private void CalculateTotal()
        {
            DataGridViewRowCollection allRows = MainDataGridView.Rows;
            if (allRows.Count > 0)
            {
                decimal totalCurrency = 0;
                PaymentRequestEntity pre;

                foreach (DataGridViewRow dgvr in allRows)
                {
                    pre = dgvr.DataBoundItem as PaymentRequestEntity;
                    totalCurrency += pre.EstimatedPayerAmount;
                }

                lblTotal.Text = totalCurrency.ToString();
            }
        }


Where are you calling GC.Collect?

You could try commenting out bits of this method to narrow the issue down further:- 1) Just calling the .Rows property exclusively. 2) Add the foreach loop back in. 3) Add the cast back in. 4) Add the call to the EstimatedPayerAmount property back in.

Is this method called automatically from the DataGrid (I don't use the DataGrid myself so I'm not sure how it works)? Could there be an exception in this method that is caught and hidden by the DataGrid?

Cheers Simon

tangent
User
Posts: 41
Joined: 30-Apr-2006
# Posted on: 07-Apr-2008 08:46:25   

Sounds like you need a profiler, I'd recommend JetBrains' dotTrace

Also if you have VS2008 you can download .NET source code and profile that as well to isolate the problem even further.. simple_smile

Posts: 93
Joined: 13-Feb-2008
# Posted on: 07-Apr-2008 14:01:03   

The memory jump is from simply iterating through the rows. If I comment out the guts of the foreach statement, I get the same memory jump. It doesn't make sense on the surface, there shouldn't be any new instances created, only new references to existing instances but when I profile the application I get new PropertyStore, IntegerProperty[], TextBoxColumns, ComboBoxColumns etc equal to recreating the entire grid...just by iterating the rows.

I've solved the problem for now by doing total calculations on the collection instead of the datagridview rows. This only solves half of the problem as I also need to calculate the totals for the selected rows and if the user selects all the rows I'll be back in the same boat.

Max avatar
Max
User
Posts: 221
Joined: 14-Jul-2006
# Posted on: 07-Apr-2008 14:04:26   

You may also try the Microsoft .Net CLR Profiler

CLR Profiler for .Net 2.0 http://www.microsoft.com/downloads/details.aspx?FamilyID=a362781c-3870-43be-8926-862b40aa0cd0&displaylang=en

CLR Profiler for .Net 1.1 http://www.microsoft.com/downloads/details.aspx?familyid=86ce6052-d7f4-4aeb-9b7a-94635beebdda&displaylang=en

One of the main purpose of CLR profiler is to analyze the memory leak.

Posts: 93
Joined: 13-Feb-2008
# Posted on: 07-Apr-2008 17:07:30   

Update:

I don't even have to iterate over the rows in code to produce the behavior. If I load the grid with 4k rows select the first row and shift-pagedown I can watch the memory grow on the same scale. Just to be clear this is without attaching to the datagrid selection changed event or calculating any totals by iterating over the rows.

Profiler reports PropertyStore.IntegerEntry[] and PropertyStore instance growth that never gets garbage collected.

Does this ring any bells with anyone?

Posts: 93
Joined: 13-Feb-2008
# Posted on: 07-Apr-2008 17:33:35   

Removing a databound combobox column lessens the growth effect. Granted having a UI where a user can list thousands of rows at a time and change a value for a row via a databound combobox is not great to begin with, but this is some seriously squirrely behavior. I keep thinking that I must have introduced something somewhere, but simply selecting all the rows via the ui and getting the same type of results points to something out of my control...

I haven't given up yet...

Otis avatar
Otis
LLBLGen Pro Team
Posts: 39588
Joined: 17-Aug-2003
# Posted on: 07-Apr-2008 18:21:55   

Without a knowledge where the memory goes to, it's hard to say what allocates all teh memory. Looking at your code, it's very weird, as all you do is reading values.

Frans Bouma | Lead developer LLBLGen Pro
Posts: 93
Joined: 13-Feb-2008
# Posted on: 09-Apr-2008 13:54:02   

After much time spent working with a memory profiler (they are so slow). I've found that simply accessing the SelectedRows property on the datagridview clones all of the selected rows. These clones appear to be the allocated memory that is never garbage collected. I assume they are cloned so the collection doesn't change while iterating (only reason I've ever cloned a collection at least)...but it doesn't appear that they are being cleaned up properly.

DataGridViewSelectedRowCollection selectedRows = MainDataGridView.SelectedRows;

Wish I could take a look at the datagridview's source code...

At this point I'm considering the following alternatives:

  1. Try a 3rd party datagridview and see if that solves it
  2. Try to extend datagridview and add a selected indexes property so that i can do calculations on the underlying collection instead of the datagridview rows. (Don't know if this is possible or not)
  3. Open a M$ support ticket (which in my experience is akin to pouring salt on an open wound)

Any other suggestions?

Posts: 93
Joined: 13-Feb-2008
# Posted on: 09-Apr-2008 15:28:30   

Work Around: Avoid iterating over the collection of selected datagridview rows by maintaining a list of selected indexes (indices) and performing all calculations on the underlying collection.


public class ACustomDataGridView : DataGridView
    {
        private List<int> selectedIndexes;

        public List<int> SelectedIndexes
        {
            get
            {
                if (this.selectedIndexes == null)
                {
                    this.selectedIndexes = new List<int>();
                }

                return this.selectedIndexes;
            }
        }

        protected override void OnRowStateChanged(int rowIndex, DataGridViewRowStateChangedEventArgs e)
        {
            base.OnRowStateChanged(rowIndex, e);

            if (e.StateChanged == DataGridViewElementStates.Selected)
            {
                if (rowIndex >= 0)
                {
                    if (!SelectedIndexes.Contains(rowIndex))
                    {
                        SelectedIndexes.Add(rowIndex);
                    }
                    else
                    {
                        SelectedIndexes.Remove(rowIndex);
                    }
                }
                else
                {
                    SelectedIndexes.Clear();
                }
            }
        }
    }

Posts: 93
Joined: 13-Feb-2008
# Posted on: 09-Apr-2008 15:53:56   

Add this to the extended datagridview...


public new void ClearSelection()
        {
            base.ClearSelection();
            SelectedIndexes.Clear();
        }

Walaa avatar
Walaa
Support Team
Posts: 14946
Joined: 21-Aug-2005
# Posted on: 09-Apr-2008 17:16:46   

Thanks for the workaround feedback.