Вопрос

Edit: Added code (Exception on line 095, 5th time it's hit.)

    public DataTable ParseBarcodes(String[] files, BarcodeZoneScan[] scanParameters)
    {
        message = null;
        //gmseBitmap img = null;
        gmseBitmap rotImg = null;
        gmseBitmap parseImage = null;
        gmseBitmap tempImage = null;

        DataTable codes = new DataTable();
        codes.Columns.Add("PageNumber");
        codes.Columns.Add("Text");
        codes.Columns.Add("Type");
        codes.Columns.Add("RegionName");

        try
        {
            gmseBarcodeInfoCollection bcc;
            gmseBarcodeReaderParameter param = new gmseBarcodeReaderParameter();
            gmseLicense.License = "plaintext license key ommited";

            String dvImageName;

            int searchCount = 0;

            for (int dvCount = 0; dvCount < files.Length; dvCount++)
            {
                if (cancelled) //If cancelled, end the loops
                {
                    dvCount = files.Length;
                    break;
                }

                dvImageName = files[dvCount].ToString();
                using (gmseBitmap img = new gmseBitmap(dvImageName))
                {

                int framecount = img.GetFrameCount();

                for (int e = 0; e < framecount; e++)
                {
                    for (int j = 0; j < scanParameters.Length; j++)
                    {
                        if (scanParameters[j].Range == PageRange.All ||//All 
                            (scanParameters[j].Range == PageRange.Even && (searchCount == 0 || searchCount % 2 == 0)) || //even
                            (scanParameters[j].Range == PageRange.Odd && (searchCount != 0 && searchCount % 2 != 0)) ||
                            (scanParameters[j].Range == PageRange.First && searchCount == 0))
                        {
                            //Setup what barcodes are going to be search for

                            param.BarcodeType = 0;
                            if (scanParameters[j].BarcodeTypes == BarcodeType.All) //All
                            {
                                param.BarcodeType = (int)gmseBarcodeType.All;
                            }
                            else
                            {
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Code39) != 0) //Code 39
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.Code39;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Code11) != 0) //Code 11
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.Code11;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Code93) != 0) //Code 93
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.Code93;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Code128) != 0) //Code 128
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.Code128;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Ean8) != 0) //EAN 8
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.EAN8;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Ean13) != 0) // EAN 13
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.EAN13;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.I2of5) != 0) //I2of5
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.i2of5;
                            }

                            param.IgnoreCheckSum = 1;
                            param.ReadMode = gmseBarcodeReadMode.WholeBitmap;

                            using (rotImg = new gmseBitmap(img.ExtractFrame(e)))
                            {

                                // do some basic image enhancement for better results
                                rotImg.ChangePixelFormat(System.Drawing.Imaging.PixelFormat.Format32bppArgb);

                                rotImg.SelectActiveFrame(e);

                                if (scanParameters[j].WholePage)
                                {
                                    parseImage = rotImg.ExtractFrame(e);
                                }
                                else
                                {
                                    using (tempImage = rotImg.ExtractFrame(e))
                                    {
                                        Rectangle convertedRect = returnConvertedRectangle(tempImage, scanParameters[j].Dimensions);
                                        if (convertedRect.IntersectsWith(new Rectangle(0, 0, tempImage.Width, tempImage.Height)))
                                        {
                                            //GC.Collect(); //Test so I can see what objects are still alive in dump
                                            parseImage = tempImage.CopyRectangle(convertedRect); //Exception here
                                        }

                                    }
                                }
                            }
                            //rotImg.Dispose();
                            //rotImg = null;

                            if (parseImage != null)
                            {
                                //Now we will apply the image enhancements:
                                if (scanParameters[j].Enhancements != ImageEnhancement.None)
                                {
                                    rotImg = EnhanceImage(parseImage, scanParameters[j].Enhancements);
                                    parseImage.Dispose();
                                    parseImage = null;
                                }

                                if ((scanParameters[j].BarcodeScanDirection & ScanDirection.LeftToRight) != 0 && !cancelled)
                                {
                                    if (parseImage == null)
                                    {
                                        tempImage = new gmseBitmap(rotImg.Image, 1);
                                    }
                                    else
                                    {
                                        tempImage = new gmseBitmap(parseImage.Image, 1);
                                    }

                                    bcc = tempImage.ReadBarcodes(param);
                                    foreach (gmseBarcodeInfo bc in bcc)
                                    {
                                        addBarcode(codes, new object[] { searchCount, bc.Text, gmseBarcodeTypeConvert(bc.BarcodeType), scanParameters[j].ZoneName });
                                    }

                                    tempImage.Dispose();
                                    tempImage = null;
                                }
                                if ((scanParameters[j].BarcodeScanDirection & ScanDirection.RightToLeft) != 0 && !cancelled)
                                {
                                    if (parseImage == null)
                                    {
                                        tempImage = new gmseBitmap(rotImg.Image, 1);
                                    }
                                    else
                                    {
                                        tempImage = new gmseBitmap(parseImage.Image, 1);
                                    }

                                    tempImage.RotateFlip(RotateFlipType.Rotate180FlipNone);

                                    bcc = tempImage.ReadBarcodes(param);
                                    foreach (gmseBarcodeInfo bc in bcc)
                                    {
                                        addBarcode(codes, new object[] { searchCount, bc.Text, gmseBarcodeTypeConvert(bc.BarcodeType), scanParameters[j].ZoneName });
                                    }

                                    tempImage.Dispose();
                                    tempImage = null;
                                }
                                if ((scanParameters[j].BarcodeScanDirection & ScanDirection.TopToBottom) != 0 && !cancelled)
                                {
                                    if (parseImage == null)
                                    {
                                        tempImage = new gmseBitmap(rotImg.Image, 1);
                                    }
                                    else
                                    {
                                        tempImage = new gmseBitmap(parseImage.Image, 1);
                                    }

                                    tempImage.RotateFlip(RotateFlipType.Rotate90FlipNone);

                                    bcc = tempImage.ReadBarcodes(param);
                                    foreach (gmseBarcodeInfo bc in bcc)
                                    {
                                        addBarcode(codes, new object[] { searchCount, bc.Text, gmseBarcodeTypeConvert(bc.BarcodeType), scanParameters[j].ZoneName });
                                    }

                                    tempImage.Dispose();
                                    tempImage = null;
                                }
                                if ((scanParameters[j].BarcodeScanDirection & ScanDirection.BottomToTop) != 0 && !cancelled)
                                {
                                    if (parseImage == null)
                                    {
                                        tempImage = new gmseBitmap(rotImg.Image, 1);
                                    }
                                    else
                                    {
                                        tempImage = new gmseBitmap(parseImage.Image, 1);
                                    }

                                    tempImage.RotateFlip(RotateFlipType.Rotate270FlipNone);

                                    bcc = tempImage.ReadBarcodes(param);
                                    foreach (gmseBarcodeInfo bc in bcc)
                                    {
                                        addBarcode(codes, new object[] { searchCount, bc.Text, gmseBarcodeTypeConvert(bc.BarcodeType), scanParameters[j].ZoneName });
                                    }

                                    tempImage.Dispose();
                                    tempImage = null;
                                }

                                if (parseImage != null)
                                {
                                    parseImage.Dispose();
                                    parseImage = null;
                                }
                                if (rotImg != null)
                                {
                                    rotImg.Dispose();
                                    rotImg = null;
                                }
                            }
                        }
                    }

                    searchCount++;

                    if (cancelled) //If cancelled, end the loops
                    {
                        e = framecount;
                        dvCount = files.Length;
                    }
                }
                } //end using img
                //img.Dispose();
                //img = null;
            }

        }
        catch (Exception ex)
        {
            message = ex.Message;
        }
        finally
        {
            if (img != null)
            {
                img.Dispose();
                img = null;
            }
            if (rotImg != null)
            {
                rotImg.Dispose();
                rotImg = null;
            }
            if (tempImage != null)
            {
                tempImage.Dispose();
                tempImage = null;
            }
            if (parseImage != null)
            {
                parseImage.Dispose();
                parseImage = null;
            }
        }

        if (!String.IsNullOrEmpty(message))
            throw new Exception(message);

        return codes;
    }

We use this GMSE Imaging plugin to assist in OCR reading barcodes from scans, it deals with skew by rotating the image by 10 degrees until it gets a read. A bug was discovered where scanning different sized sheets would throw an error.

I traced it from our main program to one of our DLLs, where I found it was catching an OutOfMemoryException.

The original TIF is 300kb, but there is a fair amount of copying done to rotate the images. (between 4 bitmaps) However I have followed the program through and monitored the locals and it appears that each bitmap is being disposed and assigned null correctly before the method at fault loops.

I've also tried adding GC.Collect() at the end of my loop.

I am on a 32bit W7 machine, which I have read has 2GB limit per object, with copious amounts of RAM so nothing lacking that that respect. Been watching it on Task Manager and my RAM usage only goes from 1.72GB to 1.78GB.

This has been a tricky one to research, as OoM seems to be an unusual occurring error. I was wondering if anyone had any advice in dealing with this kind of exception? I'm not a Visual Studio master, is there an easy way of monitoring resources/memory usage?

Or knows of any utilities I can use to assist?

Dumping the error message here, not sure how useful the code snippets would be in this situation...

System.OutOfMemoryException was caught
Message=Out of memory.
Source=System.Drawing
 StackTrace:
   at System.Drawing.Bitmap.Clone(Rectangle rect, PixelFormat format)
   at gmse.Imaging.gmseBitmap.CopyRectangle(Rectangle r)
   at ImagingInterface.ImagingFunctions.ParseBarcodes(String[] files, BarcodeZoneScan[] scanParameters) in C:\Working\Scan.backup\Global Dlls\v2.6.0.02\ScanGlobalDlls\ImagingInterface\ImagingFunctions.cs:line 632

InnerException:

(currently reading more into GC/Memory management http://msdn.microsoft.com/en-us/library/ee851764.aspx )

Working on a step of this guide, using SOS debugger in the Immediate window, with the aim of pinpointing whether the exception is generated from managed or unmanaged code.


Steps from above have indicated it's a problem with the managed code, as exception type from SOS is shown.

Exception object: 39594518
Exception type: System.OutOfMemoryException
Message: <none>
InnerException: <none>
StackTrace (generated):

The Heapdump I took doesn't seem to be thousands of bitmaps like I had kinda expected. Not 100% sure how to interpret the dump so seeing what I can find on it.

Not sure where to move from here right now! (searches..)


edit:

I have been trying to apply the lessons in this blog to my problem.

Started with PerfMon

Perfmon Graph This graph shows my program from execution to where it catches the exception.

The first two sharp peaks occur after triggering parsing of the scanned image, the last drop off occurs when the exception is caught.

Usage Summary

Q: Compare the curves for Virtual Bytes, Private Bytes and #Bytes in all Heaps, do they follow eachother or do they diverge? What is the significance of #Bytes in all heaps diverging from? (As its flat on mine)

Examined Memory with !address -summary MEM_IMAGE corresponded PrivateBytes(113MB) pretty much spot on.

Q: Where is most of the memory going (which RegionType)? RegionUsageFree 87.15% RegionUsageIsVAF 5.64% (Busy 43.89%) [memory allocated through VirtualAlloc] RegionUsageImage 5.54% (Busy 43.13%) [Memory that is mapped to a file that is part of an executable image.]

In WinDbg with SOS loaded, I did a !DumpHeap

//...
7063424c     1201        28824 System.Collections.ArrayList
706228d4      903        28896 System.EventHandler
7062f640     1253        30072 System.RuntimeType
6ec2be78      833        31216 System.Windows.Forms.PropertyStore+IntegerEntry[]
6ec2b0a4      654        34008 System.Windows.Forms.CreateParams
7063547c      318        35472 System.Collections.Hashtable+bucket[]
6ec2aa5c      664        37184 System.Windows.Forms.Control+ControlNativeWindow
70632938      716        40400 System.Int32[]
6c546700       48        49728 System.Data.RBTree`1+Node[[System.Data.DataRow, System.Data]][]
70634944       85        69600 System.Byte[]
6ec2b020      931        85972 System.Windows.Forms.PropertyStore+ObjectEntry[]
6c547758      156       161616 System.Data.RBTree`1+Node[[System.Int32, mscorlib]][]
705e6c28     2107       238912 System.Object[]
00305ce8       18       293480      Free
7062f9ac     5842       301620 System.String
Total 35669 objects

And here are the top memory hogging objects. I was hoping something would stick out like a sore thumb, like a giant amount of bitmaps or something. Is anything here scream out "I'm acting unusually!" to anyone? (I am trying to examine the top ones individually for suspect things, but would be nice to narrow down the possible culprits a bit more)

This page (Address summary explained) has been a big help. However C# is my first language, so I have no prior experience debugging memory issues. Would like to know if I am on the right track (Is GC an issue at all?) as I haven't found anything that's given me any clear indications yet.


Answer: Problem was caused in 3rd party library. Nothing I can do. Found out through deliberation and some tests with stripped down code involving just the method producing the error.

Bounty awarded to what I felt I learnt the most from.

Это было полезно?

Решение

Okay, the added info helps. The problem is not that your program uses too much memory, it uses too little. The garbage collected heap has very little data in it. That's not uncommon for a program that manipulates bitmaps. The Bitmap class is a very small wrapper around GDI+ functions, it uses only a handful of bytes in the GC heap. So you can create an enormous amount of bitmaps before you fill up the gen #0 heap and trigger a garbage collection. This is also visible from Perfmon, you want to look at the .NET CLR Memory, Gen 0 Collections counter. A healthy program triggers a collection about 10 times per second when it is doing work.

Not getting collections is fine, but there's something else that doesn't work when there are no collections. The finalizer thread never runs. Finalizers are important to release unmanaged resources other than memory. Like operating system handles and any unmanaged memory pointers held by managed objects. Bitmap has those.

First thing to do is to run Taskmgr.exe, Processes tab. Click View + Select Columns and tick Handles, USER objects and GDI objects. Observe these counters while your program is running. If you see one climbing up without bound then you have a problem that could cause GDI+ to generate an OOM exception. GDI objects being the common cause.

Carefully review your code and check that you are calling Dispose() on any Image or Bitmap that you no longer use. Beware of the subtle ones, like assigning the Image property of a PictureBox. You'd have to dispose the old one if it isn't null. That's painful of course and it is too easy to miss one. So use a simple strategy, count the number of bitmaps you created and, say, on the hundredth call GC.Collect + GC.WaitForPendingFinalizers() to trigger a collection and a finalizer sweep.

Другие советы

In the past I've always used ANTS Memory Profiler to troubleshot this sort of thing. Its not free but it works pretty pretty well for memory/reference leaks in managed code. You just take a couple of snapshots when the application should be at steady state and look at the changes.

You can safely add a using block around the img variable and with a little refactoring you can do the same to the other image-variables you are declaring.

That should at least make code more readable, and reduce the chance to forget to add one to the finally block; I may even contribute to solving the problem. You seem to be manually disposing each and every created image object, though.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top