Solved

Silverlight 4 Memory Leak

Posted on 2010-09-05
18
1,454 Views
Last Modified: 2013-11-12
I've been battling a memory leak in my SL4 app for days now. Running ANTS memory profiler indicates an unmanaged memory leak, but I can't see where this is happening.

The app is a dashboard style app that runs 24/7 and queires a WCF Service every 20s to get new data. I'm calling all closeasyc(), and nulling the client reference each time, but the memory still continues to grow.

Th interesting thing is that if I minimize the app, the memory stops growing. I found this very odd.

On timer tick i'm now only running the code below. The Bitmapimage and Image declarations at in the constructor. I did try creating on each pass, then nulling the bitmapimage and source, but this didn't help.

Does anyone know where to look for unmanaged memory leaks, as I'm out of ideas.


public void DailyElecDataSeries()
        {
            if (!Chart1Built) { CreateChart1(); }
           // Chart1Grid.Children.Clear();
            if (Chart1.Series.Count == 0)
            {
                Chart1.Series.Add(new DataSeries() { RenderAs = RenderAs.Column });
                Chart1.Series.Add(new DataSeries() { RenderAs = RenderAs.Column });
            }
            
            sDate = DateTime.Now.ToShortDateString();
            sDate2 = DateTime.Now.AddDays(-1).ToShortDateString();
            CO2Multiplier = ObjRefs[CurrentBuilding].ElecCO2;
            CarbonMultiplier = ObjRefs[CurrentBuilding].ElecCarbon;


            try
            {
                decimal maxSample=0;
                decimal consumption = 0;
                foreach (var c in ElecDayData)
                {
                    decimal dec;
                    if (Decimal.TryParse(c._day_data.ToString(), out dec))
                    {
                        consumption += dec;
                        if (dec > maxSample) maxSample = dec;


                    }
                }

                Value1 = Decimal.Round(consumption, 0).ToString() + " kWh";
                Value2 = (Decimal.Round(consumption * Convert.ToDecimal(CO2Multiplier), 0).ToString()) + " kg";
                //Value3 = (Decimal.Round((Convert.ToInt32((ElecDayData.Compute("sum(day_data)*" + CarbonMultiplier + "", "")))), 2).ToString()) + " kg";
                TodayCO2 = Convert.ToDouble(Decimal.Round(consumption * Convert.ToDecimal(CO2Multiplier), 2).ToString());

                decimal prevconsumption = 0;
                foreach (var c in ElecPrevDayData)
                {
                    decimal dec;
                    if (Decimal.TryParse(c._day_data.ToString(), out dec))
                    {
                        prevconsumption += dec;
                        if (dec > maxSample) maxSample = dec;

                    }
                }

                Value3 = Decimal.Round(prevconsumption, 0).ToString() + " kWh";
                Value4 = (Decimal.Round(prevconsumption * Convert.ToDecimal(CO2Multiplier), 0).ToString()) + " kg";
                //Value6 = (Decimal.Round((Convert.ToInt32((ElecPrevDayData.Compute("sum(day_data)*" + CarbonMultiplier + "", "")))), 2).ToString()) + " kg";

                Chart1.AxesY[0].AxisMaximum = (maxSample + 1).ToString();
                //Also add in the Axis Title at this point,X Axis Title is added in another Method
                Chart1.AxesY[0].Title = "kWh";
                
                // Set DataSeries property

                Chart1.Series[0].LegendText = "On Hours (kWh)";
                Chart1.Series[1].LegendText = "Off Hours (kWh)";


                if (count == 0)
                {
                    Chart1.Titles[0].Text = "Electricity Usage Today";
                    if (!Chart1Built)
                    {
                        foreach (var c in ElecDayData)
                        {
                            dataPoint = new DataPoint();
                            dataPoint.AxisXLabel = Convert.ToDateTime(c._Date).ToString("HH:mm");

                            // Update YValue property of the DataPoint
                            dataPoint.YValue = 0;

                            //  Add dataPoint to DataPoints collection
                            Chart1.Series[0].DataPoints.Add(dataPoint);

                            dataPoint = null;

                            dataPoint = new DataPoint();
                            dataPoint.AxisXLabel = Convert.ToDateTime(c._Date).ToString("HH:mm");

                            // Update YValue property of the DataPoint
                            dataPoint.YValue = 0;

                            //  Add dataPoint to DataPoints collection
                            Chart1.Series[1].DataPoints.Add(dataPoint);
                            dataPoint = null;

                        }
                        //Added in to allow animation of datapoints following series clearing
                        for (Int32 i = 0; i < ElecDayData.Count; i++)
                        {
                            Chart1.Series[0].DataPoints[i].YValue = ElecDayData[i]._OnHoursTotal;
                            Chart1.Series[1].DataPoints[i].YValue = ElecDayData[i]._OffHoursTotal;

                        }
                    }
                    else
                    {
                        for (Int32 i = 0; i < ElecDayData.Count; i++)
                        {
                            Chart1.Series[0].DataPoints[i].YValue = ElecDayData[i]._OnHoursTotal;
                            Chart1.Series[1].DataPoints[i].YValue = ElecDayData[i]._OffHoursTotal;

                        }
                    }
                }
                else
                {
                    Chart1.Titles[0].Text = "Electricity Usage Yesterday";
                    if (!Chart1Built)
                    {
                        foreach (var c in ElecPrevDayData)
                        {
                            dataPoint = new DataPoint();
                            dataPoint.AxisXLabel = Convert.ToDateTime(c._Date).ToString("HH:mm");

                            // Update YValue property of the DataPoint
                            dataPoint.YValue = 0;

                            //  Add dataPoint to DataPoints collection
                            Chart1.Series[0].DataPoints.Add(dataPoint);

                            dataPoint = null;

                            dataPoint = new DataPoint();
                            dataPoint.AxisXLabel = Convert.ToDateTime(c._Date).ToString("HH:mm");

                            // Update YValue property of the DataPoint
                            dataPoint.YValue = 0;

                            //  Add dataPoint to DataPoints collection
                            Chart1.Series[1].DataPoints.Add(dataPoint);
                            dataPoint = null;

                        }
                        //Added in to allow animation of datapoints following series clearing
                        for (Int32 i = 0; i < ElecPrevDayData.Count; i++)
                        {
                            Chart1.Series[0].DataPoints[i].YValue = ElecPrevDayData[i]._OnHoursTotal;
                            Chart1.Series[1].DataPoints[i].YValue = ElecPrevDayData[i]._OffHoursTotal;

                        }
                    }
                    else
                    {
                        for (Int32 i = 0; i < ElecPrevDayData.Count; i++)
                        {
                            Chart1.Series[0].DataPoints[i].YValue = ElecPrevDayData[i]._OnHoursTotal;
                            Chart1.Series[1].DataPoints[i].YValue = ElecPrevDayData[i]._OffHoursTotal;

                        }
                    }

                }
                


            }


            catch (Exception ex)
            {
                string msg = "Daily Electricity Chart Series Rendering Error: ";
                msg += ex.InnerException.ToString();
                throw new Exception(msg);

            }
            finally
            {
                if (!Chart1Built) { Chart1Grid.Children.Add(Chart1); }
                Chart1Built = true;
               
                //Chart1 = null;
                //Chart1Day=null;
                //Chart1PrevDay = null;
                //Chart1AxisY = null;




            }
        }


AND




public void ODBCData(int Count)
        {
            tBox1Grid.Children.Clear();
            tBox2Grid.Children.Clear();
            tBox3Grid.Children.Clear();
            tBox4Grid.Children.Clear();

            

            try
            {

                if (Count == 1)
                {

                    ElecPower = Convert.ToDouble(ODBCDatalist[CurrentBuilding].ElecPower);
                    ElecEquiv = Math.Round(((TodayCO2 / kgPerGallon) * MPG) / 100, 0);
                    //myGauge1.CurrentValue = Math.Round(ElecPower, 0);
                    txtStatus.Text = "Today, this Building has emitted the same CO2 as " + ElecEquiv.ToString() + " family Cars covering 100miles";
                    StatuxBox.Header = "Current Status - Electricity";
                    txtCost.Text = (Math.Round(ElecPower * Convert.ToDouble(ElecCostPerkWh), 2)).ToString() + " £/hr";
                    txtCO2.Text = (Math.Round(ElecPower * ElecCO2, 0)).ToString() + " kgCO2/h";
                    txtElecPower.Text = Math.Round(ElecPower, 1).ToString() + " kW";
                    transImg1.Source = img1;
                    transImg2.Source = img3;
                    transImg4.Source = img5;

                }
                if ((Count == 3) && (GasAvailable == 1))
                {
                    GasPower = Convert.ToDouble(ODBCDatalist[CurrentBuilding].GasPower);
                    //GasGauge.CurrentValue = Math.Round(GasPower, 0);
                    GasCO2 = Math.Round((YesterdayGasUsage * GasCO2) * HomeCO2perYear, 4);
                    txtStatus.Text = "Yesterday, this Building emitted the equivalent CO2 of  " + YesterdayGasCO2.ToString() + " Households Annual Emissions";
                    StatuxBox.Header = "Current Status - Gas";
                    txtCost.Text = (Math.Round(GasPower * Convert.ToDouble(GasCostPerkWh), 2)).ToString() + " £/h";
                    txtCO2.Text = (Math.Round(GasPower * GasCO2, 0)).ToString() + " kgCO2/h";
                    txtElecPower.Text = Math.Round(GasPower, 1).ToString() + " kW";
                    transImg1.Source = img2;
                    transImg2.Source = img4;
                    transImg4.Source = img5;

                }
                if (Count == 5)
                {
                    ElecPower = Convert.ToDouble(ODBCDatalist[CurrentBuilding].ElecPower);
                    ElecEquiv = Math.Round(((ElecPower * 1000) / LaptopPower), 0);
                    //myGauge1.CurrentValue = Math.Round(ElecPower, 0);
                    txtStatus.Text = "Today this Building has used the Equivalent Power of " + ElecEquiv.ToString() + " 40-Watt Laptop(s) per hour";
                    StatuxBox.Header = "Current Status - Electricity";
                    txtCost.Text = (Math.Round((ElecPower * Convert.ToDouble(ElecCostPerkWh)) * 12, 2)).ToString() + " £/Day";
                    txtCO2.Text = (Math.Round((ElecPower * ElecCO2) * kgCoal, 0)).ToString() + " kg Coal/h";
                    txtElecPower.Text = Math.Round(ElecPower, 1).ToString() + " kW";
                    transImg1.Source = img7;
                    transImg2.Source = img3;
                    transImg4.Source = img6;

                }
                if (Count == 7)
                {
                    ElecPower = Convert.ToDouble(ODBCDatalist[CurrentBuilding].ElecPower);
                    ElecEquiv = Math.Round(((ElecPower * 1000) / LaptopPower), 0);
                    //myGauge1.CurrentValue = Math.Round(ElecPower, 0);
                    txtStatus.Text = "Today this Building has used the Equivalent Power of " + ElecEquiv.ToString() + " 40-Watt Laptop(s) per hour";
                    StatuxBox.Header = "Current Status - Electricity";
                    txtCost.Text = (Math.Round((ElecPower * Convert.ToDouble(ElecCostPerkWh)) * 12, 2)).ToString() + " £/Day";
                    txtCO2.Text = (Math.Round((ElecPower * ElecCO2) * kgCoal, 0)).ToString() + " kg Coal/hr";
                    txtElecPower.Text = Math.Round(ElecPower, 1).ToString() + " kW";
                    transImg1.Source = img7;
                    transImg2.Source = img3;
                    transImg4.Source = img6;

                }
                if ((Count == 9) && (WaterAvailable == 1))
                {
                    WaterEquiv = Math.Round(((TodayWater * 1000) / BathUsage), 0);
                    txtStatus.Text = "Today this Building has used enough water to fill " + WaterEquiv.ToString() + " Baths";
                    StatuxBox.Header = "Current Status - Water";
                    txtCost.Text = (Math.Round((WaterCurrentRate * WaterCostm3), 2)).ToString() + " £/hr";
                    txtCO2.Text = (Math.Round((WaterCurrentRate * 1000) / BathUsage, 0)).ToString() + " Baths/hr";
                    txtElecPower.Text = Math.Round(WaterCurrentRate, 1).ToString() + " m3/h";
                    transImg1.Source = img10;
                    transImg2.Source = img9;
                    transImg4.Source = img11;

                }
                transImg3.Source = img8;


            }
            catch (Exception ex)
            {
                string msg = "ODBC Data Collection Error: ";
                msg += ex.InnerException.ToString(); ;
                throw new Exception(msg);
            }
            finally
            {

                tBox1Grid.Children.Add(transImg1);
                //System.Threading.Thread.Sleep(1000);
                tBox2Grid.Children.Add(transImg2);
                //System.Threading.Thread.Sleep(1000);
                tBox3Grid.Children.Add(transImg3);
                //System.Threading.Thread.Sleep(1000);
                tBox4Grid.Children.Add(transImg4);
                

            }


        }

Open in new window

0
Comment
Question by:wint100
  • 11
  • 7
18 Comments
 
LVL 52

Assisted Solution

by:Carl Tawn
Carl Tawn earned 500 total points
ID: 33609509
Well the first place to look would be any unmanaged resources you aer using (i.e. your images). At what point, and how, are you disposing of them?

Also any database connections/external connections could be another area. Is it possible for you to disable all of your image stuff so you can purely test the interaction with the web service?
0
 
LVL 1

Author Comment

by:wint100
ID: 33609549
Ok, originally I was using the following in the finally block:

img1=null;
transimg1=null;

But this is when I was creating the 'img1' and transimage1 in the method on each tick. I've recently changed this so that the BitmapImage(img1) is created in the 'Loaded' event of the page.

I've tried disbaling all image declerations, and am testing now. It's been running 3hours now and has gone up slightly, but not as much. Although the image method also calls a WCF Service to get the ODBCDatalist data.

As I'm testing in VS2010 debugging, could this be injecting any memory leak, or could the ASP.NET side of things be accounting for any memory leak. I'm just thinking if it's the service itself not the silverlight client.


img1 = new BitmapImage(src1);
            img2 = new BitmapImage(src2);
            img3 = new BitmapImage(src3);
            img4 = new BitmapImage(src4);
            img5 = new BitmapImage(src5);
            img6 = new BitmapImage(src6);
            img7 = new BitmapImage(src7);
            img8 = new BitmapImage(src8);
            img9 = new BitmapImage(src9);
            img10 = new BitmapImage(src10);
            img11 = new BitmapImage(src11);

Open in new window

0
 
LVL 52

Expert Comment

by:Carl Tawn
ID: 33609562
Simply setting the reference to null doesn't delete the image from memory, it simply makes the reference point to nothing.

You need to explicitly call teh Dispose() method on the image to make sure it's resources are released correctly.
0
DevOps Toolchain Recommendations

Read this Gartner Research Note and discover how your IT organization can automate and optimize DevOps processes using a toolchain architecture.

 
LVL 1

Author Comment

by:wint100
ID: 33609587
I couldn't see a dispose() method on image, that's why I'm nulling them out. Although now I'm not creating the images each pass, there should only be a single set in memory anyway, right?

It's a long grim process, that's been causing me grief for days..
0
 
LVL 52

Expert Comment

by:Carl Tawn
ID: 33609632
Are you able to comment out either all of the images, or at least most of them, in order to try and verify if it is the images that are causing the problem?
0
 
LVL 1

Author Comment

by:wint100
ID: 33609644
Currently the app is running in debug, on seems to be reclaiming memory, but the ODBCData method that uses the images is not being called. Only the DailyElecDataSeries() method above is running.

If the images are declared at constructor level, shouldn't they only be in memory once?
0
 
LVL 52

Expert Comment

by:Carl Tawn
ID: 33609666
What do you mean by "declared at contructor level"? If you mean you create the image in the constructor, then there will be a set of images for each instance of the class.
0
 
LVL 1

Author Comment

by:wint100
ID: 33609671
That's what I mean. Created in the constructor of the page. So if the single page is running 24/7, shouldn't there be a single set of images for the page, and no more created until the page is restarted?
0
 
LVL 52

Expert Comment

by:Carl Tawn
ID: 33609694
Where is the code that loads the images? Is it in the ASPX page that hosts your Silverlight app, or is it in the SL app itself?
0
 
LVL 1

Author Comment

by:wint100
ID: 33609701
Everything is in sl page code. Other than wcf.
0
 
LVL 52

Expert Comment

by:Carl Tawn
ID: 33609807
Are your images changing at any point? Only you are currently resetting the ImageSource on each pass, but if the images are static then this probably isn't necessary.
0
 
LVL 1

Author Comment

by:wint100
ID: 33609817
Yes the spurce changes depending on the count that is incremented on timer tick.

Would it help if i sent you the full page code as txt file?
0
 
LVL 52

Expert Comment

by:Carl Tawn
ID: 33609835
It would be useful to see the code in context yes. Just attach to the question.
0
 
LVL 1

Author Comment

by:wint100
ID: 33609860
Will do when I get home(1-2hrs)
0
 
LVL 1

Author Comment

by:wint100
ID: 33610337
Code attached. Feel free to criticise ANY part of this code, as I am a newb, and currently trying to teach myself the .NET  language. ANY recommendation on the code in any way is welcomed.
PageCode.txt
0
 
LVL 1

Author Comment

by:wint100
ID: 33610515
WRONG CODE ATTACHED, CORRECT CODE ATTACHED HERE..
PageCode.txt
0
 
LVL 1

Author Comment

by:wint100
ID: 33616156
Does seem odd that the memory leak is NOT present when the OOB application is minimized..
0
 
LVL 1

Accepted Solution

by:
wint100 earned 0 total points
ID: 33652274
Ok, seems there is an issue with silverlight that was causing a memory leak in Visifire charts. Visifire have modded their code to avoid ths silverlight issue, and the memory leak is pretty much clear. A big improvement anyway.
0

Featured Post

Master Your Team's Linux and Cloud Stack!

The average business loses $13.5M per year to ineffective training (per 1,000 employees). Keep ahead of the competition and combine in-person quality with online cost and flexibility by training with Linux Academy.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

For those of you who don't follow the news, or just happen to live under rocks, Microsoft Research released a beta SDK (http://www.microsoft.com/en-us/download/details.aspx?id=27876) for the Xbox 360 Kinect. If you don't know what a Kinect is (http:…
Calculating holidays and working days is a function that is often needed yet it is not one found within the Framework. This article presents one approach to building a working-day calculator for use in .NET.
This is Part 3 in a 3-part series on Experts Exchange to discuss error handling in VBA code written for Excel. Part 1 of this series discussed basic error handling code using VBA. http://www.experts-exchange.com/videos/1478/Excel-Error-Handlin…
Two types of users will appreciate AOMEI Backupper Pro: 1 - Those with PCIe drives (and haven't found cloning software that works on them). 2 - Those who want a fast clone of their boot drive (no re-boots needed) and it can clone your drive wh…

777 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question