List<T>.Clear - Does it have to be called? - c#

So I've been fighting another memory problem in my project for the past week. I tried a couple of memory profilers but nothing gave me insight into what was causing the minor memory leak. The following code turned out to be causing it:
private void DeleteAll( FlowLayoutPanel flp)
{
List<ImageControl> AllList = GetAllList(flp);
List<ImageControl> LockedList = GetLockedList(flp);
for (int i = 0; i < LockedList.Count; i++)
{
AllList.Remove(LockedList[i]);
}
flp.SuspendLayout();
for (int i = 0; i < AllList.Count; i++)
{
flp.Controls.Remove(AllList[i]);
}
DisposeList(AllList);
flp.ResumeLayout();
}
In the code, ImageControl is a UserControl, and the entire method above just removes ImageControls from a FlowLayoutPanel. The DisposList() method just calls ImageControl.Dispose() for all the controls in the list passed to it.
Now, I thought that once this method had exited, AllList would be out of scope and hence all its references to the ImageControl's would be nonexistent. So the GC would do it's stuff. But it wasn't. I found it requires
AllList.Clear();
added to the end of the DeleteAll() method, before AllList was out of scope.
So do you have to always explicitly clear a generic list to free up resources? Or is it something I'm doing wrong above? I'd like to know since I'm making fairly heavy use of temporary Lists in this project.
Ok, here's the GetAllList method. Doesn't look like a problem to me:
private List<ImageControl> GetAllList(FlowLayoutPanel flp)
{
List<ImageControl> List = new List<ImageControl>();
for (int i = 0; i < flp.Controls.Count; i++)
{
List.Add((ImageControl)flp.Controls[i]);
}
return List;
}
BTW, if you see my last couple of topics here I've been fighting memory leaks in my quest to become a proficient c# programmer :) I added the DisposeList() method since I've read Dispose() should be called on any object that implements IDisposable, which UserControl does. I also needed a way to fix up a "bug" with the ToolStrip class (which ImageControl contains), where it causes resources to remain unless the Visible property is set to false before it's destroyed. So I've overridden the Dispose method of ImageControl to do just that.
Oh, and DisposeList() also unsubscribes from an event handler:
private void DisposeList( List<ImageControl> IC )
{
for (int i=0;i<IC.Count;i++)
{
IC[i].DoEvent -= ImageButtonClick;
IC[i].Dispose();
}
}

If AllList were the only reference to the list and the elements in the list, then the list and all its elements would become eligible for garbage collection as soon as you exit the DeleteAll method.
If calling AllList.Clear() makes a difference, then I would conclude that there is a reference to the same list being held elsewhere in your code. Maybe a closer look at the GetAllList() method would give a clue where.

You shouldn't have to clear the list. Can you share your GetAllList() function? The fact that you even need a corresponding "DisposeList()" method tells me there are probably side effects there that keep a reference to your list somewhere.
Also, I'd simplify that code like this:
private void DeleteAll( FlowLayoutPanel flp)
{
var UnlockedImages = flp.Controls.OfType<ImageControl>().Except(GetLockedList(flp));
flp.SuspendLayout();
foreach (ImageControl ic in UnlockedImages)
{
flp.Controls.Remove(ic);
}
flp.ResumeLayout();
}

Related

Out of memory in image viewer despite using Image.Dispose

When adding a Next and Previous navigation option to my Image viewer coded in C#, when I press Next about 20 or so times, Visual Studio tells me the process ran out of memory. It does this in any folder with many even if the image file sizes for them all are tiny
I get:
An unhandled exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll Additional information: Out of memory.
This is the code I am using
private void next_Click(object sender, EventArgs e)
{
string[] foldernm = Directory.GetFiles(Path.GetDirectoryName(lfoto_file.FileName));
_pictureIndex++;
if (_pictureIndex >= foldernm.Length)
{
_pictureIndex = 0;
}
ibread_img.Image.Dispose();
ibread_img.Image = Image.FromFile(foldernm[_pictureIndex]);
}
Now as you can see, I have ibread_img.Image.Dispose(); there because I have searched about this and other people said to use that, but it doesn't work and I still get the same problem, a break-point confirms the code is being ran so I am confused to why its still running out of memory. The images I am cycling through are not large. I have tried everything I could find including nulling the previously loaded image, manually calling the garbage collector and nothing seems to work. I am not the best at C# so there might be a horrible mistake or flaw in that code but I don't know, any ideas on how to fix this?
There are a couple of things you could do to improve your viewer. First, you are recreating the list of image files every time; you are loading all of them each time just to access the next one and you dont have to create an image in order to show it.
// class level vars
int picIndex = 0;
IEnumerable<string> files;
int filesCount;
string picPath;
static string[] imgExts = {".png", ".jpg",".gif"};
Since you mentioned a Next and Previous button, you must have almost the same code elsewhere. This will eliminate that duplication, Next:
ShowImage(picIndex);
picIndex+=1;
if (picIndex >= filesCount)
picIndex = 0;
Then a method to show the desired image:
private void ShowImage(int Index)
{
// create image list if needed (once)
if (files == null)
{
files = new DirectoryInfo(picPath).EnumerateFiles().
Where(q => imgExts.Contains(q.Extension.ToLowerInvariant())).
Select( z => z.FullName);
filesCount = files.Count();
}
string thisFile = files.ElementAt(Index);
// no need to dispose an image if you never create one
pb2.ImageLocation = thisFile;
lblImgName.Text = Path.GetFileName(thisFile);
}
Rather that create the list of files each time (in 2 places) this does it once ever, and instead of loading a List of all of them, this leaves it as IEnumerable to get them as needed. It also works off FileInfo, is case insensitive mainly to illustrate a different way which would allow you to sort them (OrderBy) by the created date, if you wanted.
Finally, given the full path and file name, you can use the .ImageLocation property and avoid creating and disposing of Images.
The main thing is to minimize the amount of repeated code so you Dont Repeat Yourself. The code for Next and Previous is going to be almost identical.
Thanks to LarsTech and Plutonix for pointing out my mistake. This new code works fine now:
private void next_Click(object sender, EventArgs e)
{
var filteredFiles = Directory.EnumerateFiles(Path.GetDirectoryName(lfoto_file.FileName))
.Where(file => file.ToLower().EndsWith("jpg") || file.ToLower().EndsWith("png") || file.ToLower().EndsWith("gif") || file.ToLower().EndsWith("bmp") || file.ToLower().EndsWith("tiff") || file.ToLower().EndsWith("ico"))
.ToList();
_pictureIndex++;
if (_pictureIndex >= filteredFiles.Count)
{
_pictureIndex = 0;
}
ibread_img.Image.Dispose();
ibread_img.Image = Image.FromFile(filteredFiles[_pictureIndex]);
init();
}
I just needed to filter out the correct formats.

How do I fix a memory leak caused by an object referenced by a ContextMenuStrip

I have used dotMemory to locate a memory leak. The object I want gone is referenced by an event handler through a ToolStripMenuItem and ContextMenuStrip. The object contains these properties:
public override ContextMenuStrip PopupMenu
{
get
{
ContextMenuStrip myPopup = new ContextMenuStrip();
myPopup.Items.Add(ItemDelete);
return myPopup;
}
}
public ToolStripMenuItem ItemDelete
{
get
{
ToolStripMenuItem itemDelete = new ToolStripMenuItem("Delete " + name);
itemDelete.Enabled = Deletable;
itemDelete.Image = Properties.Resources.del;
itemDelete.Click += ItemDelete_Click;
return itemDelete;
}
}
I have simplified the code, the popup menu has about a dozen menu items, which all seem to be holding on to this object after I use the popup menu to delete the object. I have tried overriding the base delete method for the object to remove the handlers, but that did not work.
public override void delete()
{
if (PopupMenu != null)
{
ItemDelete.Click -= ItemDelete_Click;
}
base.delete();
}
Without a good, minimal, complete code example, it's impossible to know for certain what the problem is, never mind the best fix. That said…
Based on the tiny amount of code you've posted so far, it appears that you've misused the property feature in C#, and in doing so have obfuscated the code enough that you have been unable to recognize the bug. In particular, your ItemDelete property is creating a new object every time you call it, which is a horrible way to implement a getter of this kind. For the code to work correctly, you'd have to be careful to only ever call that property getter once, and manually cache the result somewhere else.
Creating a new object in a property getter isn't inherently bad, but it should be done only if that object will be cached by the property getter itself and reused for subsequent calls, or if that object is semantically a simple value (preferably an actual value type, but a simple, ummutable reference type would be fine too), where a newly created object is functionally identical to any previously created object (i.e. they can be used interchangeably without affecting the correctness of the code).
Given the above, it is possible that you would find the following alternative a useful way to fix the problem:
private Lazy<ToolStripMenuItem> _itemDelete =
new Lazy<ToolStripMenuItem>(() => _CreateItemDelete());
private ToolStripMenuItem _CreateItemDelete()
{
ToolStripMenuItem itemDelete = new ToolStripMenuItem("Delete " + name);
itemDelete.Enabled = Deletable;
itemDelete.Image = Properties.Resources.del;
itemDelete.Click += ItemDelete_Click;
return itemDelete;
}
public ToolStripMenuItem ItemDelete
{
get
{
return _itemDelete.Value;
}
}
This will defer creation of the ToolStripMenuItem object until the first time that the property getter is called, but will on subsequent calls to the getter return that object created on the first call.
In this way, you will ensure that when you execute the statement ItemDelete.Click -= ItemDelete_Click; later, you are actually removing the event handler from the original object, rather than some new object you created at that time.

Monotouch ipad memory / animation problem

All, I'm working on what I thought was a fairly simple app. I'm using multiple view controllers with a view - under which there are buttons and a single image view. the buttonpress event triggers the other viewcontroller's view to display. That works perfectly. However, I'm also wanting to animate the transition to simulate a page turn. I use the code below to do that. It works well, however, every time I use this method the memory used increases. The memory used appears to be disconnected from the actual size of the image array. Also, I changed from png to jpeg ( much smaller images ) and it doesn't make a bit of difference. I thought about using .mov but the load time is very noticeable.
Please help. I've tried a ton of different way to force garbage collection. I've dug through the limited texts, and searched this website to no avail.
Here's a sample of the code.
public partial class AppDelegate : UIApplicationDelegate
{
// This method is invoked when the application has loaded its UI and its ready to run
public override bool FinishedLaunching (UIApplication app, NSDictionary options)
{
UIApplication.SharedApplication.SetStatusBarHidden( true, true);
// If you have defined a view, add it here:
// window.AddSubview (navigationController.View);
//window.AddSubview(mainController.View);
window.MakeKeyAndVisible ();
coverOpenbtn.TouchUpInside += HandleCoverOpenbtnTouchUpInside;
backBtn1.TouchUpInside += HandleBackBtn1TouchUpInside;
return true;
}
void HandleBackBtn1TouchUpInside (object sender, EventArgs e)
{
this.navView.RemoveFromSuperview();
List<UIImage> myImages = new List<UIImage>();
myImages.Add(UIImage.FromFile("c_1_00011.jpg"));
myImages.Add(UIImage.FromFile("c_1_00010.jpg"));
myImages.Add(UIImage.FromFile("c_1_00009.jpg"));
myImages.Add(UIImage.FromFile("c_1_00008.jpg"));
myImages.Add(UIImage.FromFile("c_1_00007.jpg"));
myImages.Add(UIImage.FromFile("c_1_00006.jpg"));
myImages.Add(UIImage.FromFile("c_1_00005.jpg"));
myImages.Add(UIImage.FromFile("c_1_00004.jpg"));
myImages.Add(UIImage.FromFile("c_1_00003.jpg"));
myImages.Add(UIImage.FromFile("c_1_00002.jpg"));
myImages.Add(UIImage.FromFile("c_1_00001.jpg"));
myImages.Add(UIImage.FromFile("c_1_00000.jpg"));
//myImages.Add(UIImage.FromFile("c_1_00012.jpg"));
var myAnimatedView = new UIImageView(window.Bounds);
myAnimatedView.AnimationImages = myImages.ToArray();
myAnimatedView.AnimationDuration = 1; // Seconds
myAnimatedView.AnimationRepeatCount = 1;
myAnimatedView.StartAnimating();
window.AddSubview(myAnimatedView);
}
void HandleCoverOpenbtnTouchUpInside (object sender, EventArgs e)
{
this.coverView.AddSubview(navView);
List<UIImage> myImages = new List<UIImage>();
myImages.Add(UIImage.FromFile("c_1_00000.jpg"));
myImages.Add(UIImage.FromFile("c_1_00001.jpg"));
myImages.Add(UIImage.FromFile("c_1_00002.jpg"));
myImages.Add(UIImage.FromFile("c_1_00003.jpg"));
myImages.Add(UIImage.FromFile("c_1_00004.jpg"));
myImages.Add(UIImage.FromFile("c_1_00005.jpg"));
myImages.Add(UIImage.FromFile("c_1_00006.jpg"));
myImages.Add(UIImage.FromFile("c_1_00007.jpg"));
myImages.Add(UIImage.FromFile("c_1_00008.jpg"));
myImages.Add(UIImage.FromFile("c_1_00009.jpg"));
myImages.Add(UIImage.FromFile("c_1_00010.jpg"));
myImages.Add(UIImage.FromFile("c_1_00011.jpg"));
//myImages.Add(UIImage.FromFile("c_1_00012.jpg"));
var myAnimatedView = new UIImageView(window.Bounds);
myAnimatedView.AnimationImages = myImages.ToArray();
myAnimatedView.AnimationDuration = 1; // Seconds
myAnimatedView.AnimationRepeatCount = 1;
opened++;
}
myAnimatedView.StartAnimating();
window.AddSubview(myAnimatedView);
}
Here's a few hints (just by reading the code):
There no difference between JPEG and PNG once the images are loaded in memory. The format only matters when the image is stored, not displayed. Once loaded (and decompressed) they will take a bit over (Width * Height * BitCount) of memory.
Consider caching your images and load them only they are not available. The GC will decide when to collect them (so many copies could exists at the same time). Right now you're loading each image twice when you could do it once (and use separate array for ordering them).
Even if you cache them also be ready to clear them on demand, e.g. if iOS warns you memory is low. Override ReceiveMemoryWarning to clear your list (or better arrays).
Don't call ToArray if you can avoid it (like your sample code). If you know how many images you have them simply create the array with the right size (and cache both array too ;-). It will cut down (a bit) the allocations;
Even consider caching the 'myAnimatedView' UIImageView (if the above did not help enough)
Be helpful to others, try them one-by-one and tell us what help you the most :-)
The images are to "animate" a page turn...is this to navigate through the app?
E.g. you start at the "home" page, press a button then it animates a page turn to the next screen in your app?
I think you would be better off looking at using CoreGraphics to try and achieve this effect, it'll both be a lot more efficient memory wise, and it will probably look a lot better as well. There are a few projects in Objective-C to get you started, such as Tom Brow's excellent Leaves project.
Okay, here is the best solution I found, doesn't crash the hardware, and is generally useful for other tasks.
This is the code that goes in the handler for the button press, each NavImage is a UIImage I built under the same view in interface builder. I just turned the alpha to 0 initially, and light them up one by one...
NSTimer.CreateScheduledTimer(.1,delegate { navImage1.Alpha = 1; NSTimer.CreateScheduledTimer(.1,delegate { navImage2.Alpha = 1;
NSTimer.CreateScheduledTimer(.05,delegate { navImage3.Alpha = 1;
NSTimer.CreateScheduledTimer(.05,delegate { navImage4.Alpha = 1;
NSTimer.CreateScheduledTimer(.05,delegate { navImage5.Alpha = 1;
NSTimer.CreateScheduledTimer(.05,delegate { navImage6.Alpha = 1;
NSTimer.CreateScheduledTimer(.05,delegate { navImage7.Alpha = 1;
NSTimer.CreateScheduledTimer(.05,delegate { navImage8.Alpha = 1;
NSTimer.CreateScheduledTimer(.05,delegate { navImage9.Alpha = 1;
NSTimer.CreateScheduledTimer(.05,delegate { navImage.Alpha = 1; });});});});});});});});});});

Assigning a value to a global variable from inside of event handler?

I'm working on a program but an issue i was faced to keep me worried.I'm kind of novice and i'm building this program for a competition.The code where the problem lies is like following :
class Blabla : Usercontrol
{
public List<string> mainList;
public Blabla()
{
mainList = new List<string>();
something.DownloadStringCompleted += new DownloadStringCompletedEventHandler(xx_DownloadStringCompleted);
}
void xx_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
List<string> abc = SomeMethod(e.Result);
mainList = abc;
}
}
I try it.Even though "abc" variable has the value i want , mainList remains empty.I don't know why and how to make it work.That's why i need some hand.Thank you.
Variable abc has the value you want UNTIL you get out of your event handler, probably, when it gets deleted because it uses e.Result directly.
Familiarize yourself with .Clone() method and IClonable interface, and try creating a COPY of the list that is in question, not the reference.
If abc is a list, mainList will be set to the same list. You don't have to clone the list, it should stay active because there is a reference to it, and therefore it doesn't get garbage collected.
When you said that mainList was empty, did you look at it in the debugger immediately after setting it in the xx_DownloadStringCompleted method? Or are you looking at it somewhere else in your program?
I would guess that this is a threading issue. Does your event handler get called from a different thread? If so, you would need to add some synchronization logic in order to guarantee that mainList is available to your other thread.

WHY! doesn't this dispose of all the objects on the flow panel?

WHY! doesn't this dispose of all the objects on the flow panel!?
the count shows 5 and there are 5 buttonsWithProperties on the form, no other objects are on the form.
foreach (ButtonWithProperties itemButton in flowLayoutPanel1.Controls)
{
itemButton.Dispose();
}
It disposes 3 of the object but not the last 2...
Indeed it's a bad practice to Dispose() an object to which there is a live reference (in your visual tree). If you want to remove the buttons, you perhaps have to remove them from Controls in an orderly way. See http://msdn.microsoft.com/en-us/library/system.windows.forms.control.controlcollection.removeat.aspx.
Edit:
Please note that the button is an IDisposable, therefore the version with RemoveAt needs an explicit Dispose:
var controls = flowLayoutPanel1.Controls;
for (int i = controls.Count - 1; i >= 0; --i)
{
var c = controls[i];
if (c is ButtonWithProperties)
{
flowLayoutPanel1.Controls.RemoveAt(i);
c.Dispose();
}
}
Edit:
The documentation suggests that Dispose should be called even if one is using Clear. So if you don't need the buttons any more, you should Dispose() them.

Categories

Resources