Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# Bitmap - Cannot find how to remove OutOfMemoryException

If I execute the code below, OutOfMemoryException occurs at either the line

using (Bitmap bitmap1 = new Bitmap(FrameToFilePath(interval.Start - 1)))

or the line

using (Bitmap bitmap2 = new Bitmap(FrameToFilePath(interval.End + 1)))

when the inner for statement executed about 1000 times.

However, I don't know why OutOfMemoryException occurs. I think I have written enough using to dispose Bitmap objects. Where does the memory leak occurs?

class Program
{
    static void Main(string[] args)
    {
        // Some code to initialize List<Interval> intervals

        Parallel.ForEach(intervals, interval =>
        {
            using (Bitmap bitmap1 = new Bitmap(FrameToFilePath(interval.Start - 1)))
            using (Bitmap bitmap2 = new Bitmap(FrameToFilePath(interval.End + 1)))
            {
                for (int i = interval.Start; i <= interval.End; i++)
                {
                    ColorMatrix colorMatrix = new ColorMatrix(); // Identity matrix
                    colorMatrix.Matrix33 = (i - interval.Start + 1F) / (interval.Span + 1F); // Alpha

                    using (ImageAttributes imageAttributes = new ImageAttributes())
                    using (Bitmap intermediate = new Bitmap(bitmap1.Width, bitmap1.Height, PixelFormat.Format32bppArgb))
                    using (Graphics graphics = Graphics.FromImage(intermediate))
                    {
                        imageAttributes.SetColorMatrix(colorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

                        graphics.CompositingMode = System.Drawing.Drawing2D.CompositingMode.SourceOver;

                        graphics.DrawImage(bitmap1, 0, 0);
                        graphics.DrawImage(bitmap2, new Rectangle(0, 0, intermediate.Width, intermediate.Height), 0, 0, bitmap2.Width, bitmap2.Height, GraphicsUnit.Pixel, imageAttributes);

                        intermediate.Save(FrameToFilePath(i), ImageFormat.Png);
                    }
                }
            }
        });
    }

    static string FrameToFilePath(int frame)
    {
        return string.Format(@"C:\(some path)\frames\frame-{0:00000}.png", frame);
    }
}

class Interval
{
    public int Start { get; set; }
    public int End { get; set; }
    public int Span { get { return End - Start + 1; } }
}

EDIT: OK. Maybe it's because Parallel.ForEach spawns tasks before other tasks ended, so that more and more tasks are started but Bitmaps were not GCed since the tasks were not ended. Well, if this is the case, how to fix it to avoid OutofMemoryException? I am not sure if this is the case yet though...

like image 287
Naetmul Avatar asked Jan 04 '15 15:01

Naetmul


1 Answers

I ran into a similar (but not similar enough for me to want to mark this as a duplicate) issue myself. What you can do is set the ParallelOptions.MaximumDegreeOfParallelism to not create extra tasks if it sees work is stalled.

Parallel.ForEach(intervals,
    new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount },
    (interval) =>
        {
           //...
        });

The reason you are getting this problem is the thing you are bound by is not the CPU but the IO of the drive during the reading of the file returned from FrameToFilePath. This causes a low CPU % and Parallel.ForEach detects that low % and tries to spawn more tasks to bring the usage up to 100%. Setting the max parallelism tells it to stop trying to reach 100% once a set number of tasks has been created. You could use a number larger than Environment.ProcessorCount but your main bottleneck is your file IO.

like image 130
Scott Chamberlain Avatar answered Oct 11 '22 19:10

Scott Chamberlain