codebude / QRCoder

A pure C# Open Source QR Code implementation
MIT License
4.61k stars 1.1k forks source link

Exception thrown calling GetGraphic #121

Closed MikeTheWatchGuy closed 6 years ago

MikeTheWatchGuy commented 6 years ago

After generating several hundred QR Codes, I'm getting an exception when calling GetGraphic. The exception is:

System.ArgumentException: 'Parameter is not valid.'

Sample code:

                QRCodeData qrCodeData = qrGenerator.CreateQrCode(chunkAsString, QRCodeGenerator.ECCLevel.Q);
                QRCode qrCode = new QRCode(qrCodeData);
                Bitmap qrCodeImageBitmap = qrCode.GetGraphic(QRPixelsPerModule);

After each code I'm calling Dispose for qrCode, qrCodeImagBitmap, qrCodeData.

It seems like a memory / resource leak. Once I get one of these it always throws the exception.

I do have a legitimate reason for making 100s of calls from my app in a single session. I'm not able to break apart this particular task into smaller groups of bar codes.

Type of issue

[*] Bug
[ ] Question (e.g. about handling/usage)
[ ] Request for new feature/improvement

Expected Behavior

Current Behavior

Possible Solution (optional)

Steps to Reproduce (for bugs)

Loop making the call sequence to create Bitmap QR Code. Do this several hundred times. Eventually you should hit this exception.

Your Environment

Include as many relevant details about the environment you experienced the bug in.

codebude commented 6 years ago

Hi Mike,

the error message (System.ArgumentException: 'Parameter is not valid.') is quite generic, but usually this comes from the underlying GDI+ and yes it could be a memory leak. But since I know, that there a users of QRCoder, who a generating several thousands of QRCodes without any problem, let me ask how you dispose the objects? (You wrote you would dispose, but in your example code I can't see anything like that.)

In addition could you please try, if you still ge the errors if you use using-clauses in your code, like that:

QRCodeGenerator qrGenerator = new QRCodeGenerator();
using (QRCodeData qrCodeData = qrGenerator.CreateQrCode(chunkAsString, QRCodeGenerator.ECCLevel.Q))
{
  using (Bitmap qrCodeImageBitmap = new QRCode(qrCodeData).GetGraphic(QRPixelsPerModule))
  {
    //Do what ever you want with qrCodeImageBitmap
  }
}
MikeTheWatchGuy commented 6 years ago

Thank you very much for the fast reply and the code!

I dropped your code in, adding all my processing where you've got the comment. Unfortunately it crashes after roughly the same amount of time.

Regardless, I clearly have much more to learn about C# resources as I wasn't using a context manager. I was manually disposing of each object. At the end of my processing I called: qrCodeImageBitmap.Dispose(); qrCodeImageBitmapResized.Dispose(); qrCode.Dispose(); qrCodeData.Dispose(); frame.Dispose();

My basic logic is to encode "batches" of codes that less than 100 in each batch. After about 500 total QR Codes I get the crash.

I also get this in my output during my batches: Exception thrown: 'System.FormatException' in mscorlib.dll Exception thrown: 'System.FormatException' in mscorlib.dll Exception thrown: 'System.FormatException' in mscorlib.dll

All codes are generated just fine however.

Exception thrown: 'System.FormatException' in mscorlib.dll Exception thrown: 'System.ArgumentException' in System.Drawing.dll An unhandled exception of type 'System.ArgumentException' occurred in System.Drawing.dll Parameter is not valid.

I do apologize for my recent arrival to C#. I've got decades of other languages and environments, but C# has been the newest addition.

Other than these long batches being a problem the QR generator code has been working PERFECTLY. The codes it generates are working well with my decoders.

codebude commented 6 years ago

Hi Mike,

to check if there's really a leak with the underlying GDI+ objects, you could do two thigs:

  1. Open your app. Then open taskmanager and look if the GDI value increases until your crash occurs. (In my test app, I can create as much QRCodes as I want - the GDI value stays constant.)
    • Windows 8/10: Switch to "Details" tab, right-click the column header and add the column "GDI"
    • Windows <=7: Switch to "Process" tab, right-click the column header and add the column "GDI"
  2. If the value increases, use GDIView to identify the types of GDI objects which raise. Maybe we can get a clue at which point the memory isn't released.

In addition I can offer you to review your code. Maybe there's another thing, which leads to your problems. (If you don't want to show the code public, you may also send me a mail.)

MikeTheWatchGuy commented 6 years ago

I ran some tests and found that indeed memory usage is climbing.

I'm able to duplicate the problem much quicker by setting the QR Module Size to 80 pixels. This will cause large bitmaps to be created.

After each call I lose about 200 MB of memory when I'm making the QR codes. In this test I run out of memory before I finish my batch.

If I make the Module Size 4 then I'm able to finish my batch run.

I'm still working on getting data from GDI View. More testing required.

codebude commented 6 years ago

When calling it in a loop without disposing or using clauses GDI memory won't be free'd fast enough. That's not a fault of the lib but more about the context of usage. I think we could clear this topic in the meantime, so I'll close the issue. If the problems come back, feel free to reopen the issue.

MikeTheWatchGuy commented 6 years ago

Yes, please close.

A note for others... using a "large" value for Module Size creates very large objects. I was using a value of 12 with payloads of 200 characters, a somewhat complex code. And, I was making many of these inside of a loop.

Lowering the Module Size to 2 dropped my memory usage significantly so that I don't see this problem.

Thank you for the source code! It's being used. And thanks for the fast reply.