"Simple" animation in C# --> memory leaks???

  • Thread starter Beam_Us_Up_Scotty
  • Start date
B

Beam_Us_Up_Scotty

Hello all,

I am trying to write a "simple" animation using C#, and I've tried many
things but nothing seems to work for me without leaking memory. Here's
a very simple piece of code that uses a timer to set the image of a
label from an ImageList every 100ms or so. This is what was being used
when I inherited this piece of code, and I thought it was OK, until I
changed the size of the label to do animation on a larger piece of
screen real-estate.

-----------------------------------------
public void Timer_Tick(object sender, EventArgs e)
{
if (isAnimating)
{
pic_index = ++pic_index % 18;
this.label1.ImageIndex = pic_index;
}
else
{
this.timer1.Enabled = false;
}
}

-----------------------------------------

The form has a start and stop button, and the label. The imagelist has
18 images in it. I start it and it runs and leaks memory like a sieve!
The "in memory" footprint will shrink if I minimize the app, but we're
running on an embedded PC and there is NO virtual memory available, so
we are running full screen all the time.

I've tried other approaches such as double buffering, and that leaks
about 4k bytes / second at 10 refreshes per second. Here's the "tick"
method from that attempt:

-----------------------------------------
public void Timer_Tick(object sender, EventArgs e)
{
if (isAnimating)
{
pic_index = ++pic_index % 18;
Graphics AnimG = screen.getGraphics(); // gets an offscreen "copy"
screen.erase();
spriteArray[pic_index].draw(AnimG); // draw to the copy
screen.flip(); // blt
AnimG = null;
}
else
{
this.timer1.Enabled = false;
}
}
-----------------------------------------

The imagelist example acts like it's "copying" the image out of the
image list and storing it into some permanent (semi-permanent) data
structure someplace and keeping a strong reference to it. I don't even
have a theory as to what the double-buffering sample is doing, how it
could be leaking 4k bytes per second, any new objects created in the
tick method are destroyed at the end of it! In that method, I have 18
"sprites" where one gets painted to the buffer each tick and the buffer
is blt-d to the screen on a panel. That's about as simple as you can
make it.

The only thing I can figure is I'm using the timer wrong - somehow.
I've copied samples off the 'net, I don't "start" the timer anymore,
just enable or disable it.

Anybody have any clues as to what I've done wrong? I've done
animations for years in Java, and NEVER seen anything like this. I
"stumbled" on the memory leak issue because we ran our embedded PC out
of memory so fast it (the app not the PC) crashed in about 30s.

I'm frustrated and developing a VERY bad taste for C#.



Thanks in advance.
 
J

Justin

I haven't done enough coding with the compact framework to be sure what
is in it, but I feel that some calls to the garbage collector could
help. I say this because you say the memory issue is solved with
minimizing. In regular C# coding you can invoke the garbage collector
with the code GC.Collect(). So try and see if that is in the CF or not
and if so see what happens when you call it after every redraw.

Also, as for the timer, I prefer to use Thread.Sleep() for timed
events. It just seems to be more solid. Hope this helps ~ Justin
 
B

Beam_Us_Up_Scotty

Thanks Justin,

That does seem to work. But why on earth does it require an _explicit_
call to GC in order to recover memory, and why does the app happily
crash before at least trying to run GC?

Microsoft - are you listening? Is this half-baked or what?

-Scotty
 
G

Guest

Beam_Us_Up_Scotty said:
Thanks Justin,

That does seem to work. But why on earth does it require an _explicit_
call to GC in order to recover memory, and why does the app happily
crash before at least trying to run GC?

Microsoft - are you listening? Is this half-baked or what?

MS product feedback and bug reporting site.

http://lab.msdn.microsoft.com/productfeedback/default.aspx

Not keeping enough memory free to allow the GC to collect without requesting
more from the system does appear to be a bug, probably from a missed test
case.
 
R

Richard Grimes

Justin said:
Also, as for the timer, I prefer to use Thread.Sleep() for timed
events. It just seems to be more solid. Hope this helps ~ Justin

Note that Thread.Sleep() will 'sleep' the current thread, but it also
allows other threads to work. I guess this is why you find it more
'solid' because calling it will not impact other work being done on
other threads in the process. Indeed, if you want to 'switch' to another
thread in your process on a single processor machine you can call
Thread.Sleep(0).

Richard
 
R

Richard Grimes

Beam_Us_Up_Scotty said:
-----------------------------------------
public void Timer_Tick(object sender, EventArgs e)
{
if (isAnimating)
{
pic_index = ++pic_index % 18;
Graphics AnimG = screen.getGraphics(); // gets an offscreen "copy"
screen.erase();

What is this screen object? How is getGraphics implemented?

I suspect you are doing something like a call to Graphics.FromHWND() or
FromImage(). If you implement it using one of these methods (or similar)
then you must release the graphics object after you have finished using
it, see below...
spriteArray[pic_index].draw(AnimG); // draw to the copy
screen.flip(); // blt
Anim.Dispose();

AnimG = null;

Doing this will just mark the object as being available for garbage
collection sometime in the future, but you have no idea when that will
be. The graphics object's finaliser effectively calls Dispose(). Calling
Dispose explicitly will clean up the resources immediately.

Better still, the device context will not change during the animation,
so you do not need to create a new Graphics object for each tick.
Instead, create it once and then cache it. Therefore you
screen.getGraphics will return the cached Graphics object. In this case
DO NOT Dispose the Graphics object in the tick method.
I'm frustrated and developing a VERY bad taste for C#.

To be honest, this has nothing to do with C#, it is an issue with
non-deterministic finalization, which is a .NET issue, and anyway, Java
has just the same problems.

Richard
 
W

Willy Denoyette [MVP]

Beam_Us_Up_Scotty said:
Thanks Justin,

That does seem to work. But why on earth does it require an _explicit_
call to GC in order to recover memory, and why does the app happily
crash before at least trying to run GC?

Microsoft - are you listening? Is this half-baked or what?

-Scotty

Images are unmanaged resources (they are loaded in the process heap not in
the GC heap), so you need to call Dispose to release them, the GC has
nothing to do with unmanaged memory. When calling GC.Collect() you force the
finalizer to run, and the finalizer will call Finally" on the Graphics
object and this one will release the unmanaged memory, but you shouldn't do
that, call Dispose or use a using statement block like this:.

pic_index = ++pic_index % 18;
using(Graphics AnimG = screen.getGraphics())
{
screen.erase();
spriteArray[pic_index].draw(AnimG); // draw to the copy
screen.flip(); // blt
// no need to set AnimG to nul here, this is taken care of by the Finaly
method.
}


Willy.
 
W

Willy Denoyette [MVP]

Justin said:
I haven't done enough coding with the compact framework to be sure what
is in it, but I feel that some calls to the garbage collector could
help. I say this because you say the memory issue is solved with
minimizing. In regular C# coding you can invoke the garbage collector
with the code GC.Collect(). So try and see if that is in the CF or not
and if so see what happens when you call it after every redraw.

Also, as for the timer, I prefer to use Thread.Sleep() for timed
events. It just seems to be more solid. Hope this helps ~ Justin
You should never call GC.Collect on disposable objects, Dispose them of when
done with them.

Willy.
 
W

Willy Denoyette [MVP]

Dan Neely said:
MS product feedback and bug reporting site.

http://lab.msdn.microsoft.com/productfeedback/default.aspx

Not keeping enough memory free to allow the GC to collect without
requesting
more from the system does appear to be a bug, probably from a missed test
case.

Not sure where you get this from, Graphic objects do wrap unmanaged
resources (memory and GDI handles) and therefore implements the Dispose
pattern, failing to call Dispose in a timely fashion (say when done with it)
will result in a program failure as you will exhaust the GDI objects handles
(these are limited unmanaged resources) available to the process, the GC
will never kick in here because it has no knowledge of such resources.


Willy.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top