I am making a scanning component, but when I set a high resolution for the document (600 dpi), I tend to get System.OutOfMemoryException after just 1 or 2 scans.
My code is as follows
public ScannedImage SaveScannedImage(DataTransferredEventArgs e)
{
if (e == null) throw new IOException();
BitmapSource fullResImage;
using (var fullResImageStream = e.GetNativeImageStream())
{
fullResImage = fullResImageStream.ConvertToWpfBitmap(e.ImageInfo.ImageWidth, e.ImageInfo.ImageLength);
}
BitmapSource lowResImage;
using (var lowResImageStream = e.GetNativeImageStream())
{
lowResImage = lowResImageStream.ConvertToWpfBitmap(800, 0);
}
return new ScannedImage(lowResImage, fullResImage);
}
It is usually happening at the
using (var lowResImageStream = e.GetNativeImageStream())
Help would be much appreciated.
What you see may be caused by large object heap (LOH) fragmentation.
That is hard to avoid, but you can compact the LOH explicitely.
GCSettings.LargeObjectHeapCompactionMode =
GCLargeObjectHeapCompactionMode.CompactOnce;
GC.Collect();
Also, make sure you run as a 64 bit process. Turn of the "Prefer 32 bit" option if it is on.
For more information, you might want to read
https://msdn.microsoft.com/en-us/library/system.runtime.gcsettings.largeobjectheapcompactionmode.aspx
https://blogs.msdn.microsoft.com/ericlippert/2009/06/08/out-of-memory-does-not-refer-to-physical-memory/
Related
I am trying to get total GPU memory value. I tried with WMI but it returns not higher value than 4gb.
After reading few articles I found out Cudafy.
public static ulong GetGpuMemory()
{
GPGPU gpu = CudafyHost.GetDevice(CudafyModes.Target, CudafyModes.DeviceId);
var c = gpu.GetDeviceProperties(true);
var p = c.TotalMemory;
return p;
}
For some reason it is returning max ulong value.
Any ideas?
Do you know any other way to get correct GPU memory value from both - NVIDIA and AMD GPUs? What about taking hex value from registry and then converting it? I tried to write some code but without success. Thanks for any help in advance
This should work:
using Microsoft.Win32;
...
long getvideomem()
{
try
{
using (RegistryKey key = Registry.LocalMachine.OpenSubKey(#"SYSTEM\ControlSet001\Control\Class\{4d36e968-e325-11ce-bfc1-08002be10318}\0000"))
{
if (key != null)
{
object o = key.GetValue("HardwareInformation.qwMemorySize");
if (o != null)
return (long)o;
}
}
}
catch { }
return 0;
}
Multiple GPU systems would need some tweaks however.
I have been playing around with SharpDX.XAudio2 for a few days now, and while things have been largely positive (the odd software quirk here and there) the following problem has me completely stuck:
I am working in C# .NET using VS2015.
I am trying to play multiple sounds simultaneously.
To do this, I have made:
- Test.cs: Contains main method
- cSoundEngine.cs: Holds XAudio2, MasteringVoice, and sound management methods.
- VoiceChannel.cs: Holds a SourceVoice, and in future any sfx/ related data.
cSoundEngine:
List<VoiceChannel> sourceVoices;
XAudio2 engine;
MasteringVoice master;
public cSoundEngine()
{
engine = new XAudio2();
master = new MasteringVoice(engine);
sourceVoices = new List<VoiceChannel>();
}
public VoiceChannel AddAndPlaySFX(string filepath, double vol, float pan)
{
/**
* Set up and start SourceVoice
*/
NativeFileStream fileStream = new NativeFileStream(filepath, NativeFileMode.Open, NativeFileAccess.Read);
SoundStream soundStream = new SoundStream(fileStream);
SourceVoice source = new SourceVoice(engine, soundStream.Format);
AudioBuffer audioBuffer = new AudioBuffer()
{
Stream = soundStream.ToDataStream(),
AudioBytes = (int)soundStream.Length,
Flags = SharpDX.XAudio2.BufferFlags.EndOfStream
};
//Make voice wrapper
VoiceChannel voice = new VoiceChannel(source);
sourceVoices.Add(voice);
//Volume
source.SetVolume((float)vol);
//Play sound
source.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo);
source.Start();
return voice;
}
Test.cs:
cSoundEngine engine = new cSoundEngine();
total = 6;
for (int i = 0; i < total; i++)
{
string filepath = System.IO.Directory.GetParent(System.IO.Directory.GetCurrentDirectory()).Parent.FullName + #"\Assets\Planet.wav";
VoiceChannel sfx = engine.AddAndPlaySFX(filepath, 0.1, 0);
}
Console.Read(); //Input anything to end play.
There is currently nothing worth showing in VoiceChannel.cs - it holds 'SourceVoice source' which is the one parameter sent in the constructor!
Everything is fine and well running with up to 5 sounds (total = 5). All you hear is the blissful drone of Planet.wav. Any higher than 5 however causes the console to freeze for ~5 seconds, then close (likely a c++ error which debugger can't handle). Sadly no error message for us to look at or anything.
From testing:
- Will not crash as long as you do not have more than 5 running sourcevoices.
- Changing sample rate does not seem to help.
- Setting inputChannels for master object to a different number makes no difference.
- MasteringVoice seems to say the max number of inputvoices is 64.
- Making each sfx play from a different wav file makes no difference.
- Setting the volume for sourcevoices and/or master makes no difference.
From the XAudio2 API Documentation I found this quote: 'XAudio2 removes the 6-channel limit on multichannel sounds, and supports multichannel audio on any multichannel-capable audio card. The card does not need to be hardware-accelerated.'. This is the closest I have come to finding something that mentions this problem.
I am not well experienced with programming sfx and a lot of this is very new to me, so feel free to call me an idiot where appropriate but please try and explain things in layman terms.
Please, if you have any ideas or answers they would be greatly appreciated!
-Josh
As Chuck has suggested, I have created a databank which holds the .wav data, and I just reference the single data store with each buffer. This has improved the sound limit up to 20 - however this has not fixed the problem as a whole, likely because I have not implemented this properly.
Implementation:
class SoundDataBank
{
/**
* Holds a single byte array for each sound
*/
Dictionary<eSFX, Byte[]> bank;
string curdir => Directory.GetParent(Directory.GetCurrentDirectory()).Parent.FullName;
public SoundDataBank()
{
bank = new Dictionary<eSFX, byte[]>();
bank.Add(eSFX.planet, NativeFile.ReadAllBytes(curdir + #"\Assets\Planet.wav"));
bank.Add(eSFX.base1, NativeFile.ReadAllBytes(curdir + #"\Assets\Base.wav"));
}
public Byte[] GetSoundData(eSFX sfx)
{
byte[] output = bank[sfx];
return output;
}
}
In SoundEngine we create a SoundBank object (initialised in SoundEngine constructor):
SoundDataBank soundBank;
public VoiceChannel AddAndPlaySFXFromStore(eSFX sfx, double vol)
{
/**
* sourcevoice will be automatically added to MasteringVoice and engine in the constructor.
*/
byte[] buffer = soundBank.GetSoundData(sfx);
MemoryStream memoryStream = new MemoryStream(buffer);
SoundStream soundStream = new SoundStream(memoryStream);
SourceVoice source = new SourceVoice(engine, soundStream.Format);
AudioBuffer audioBuffer = new AudioBuffer()
{
Stream = soundStream.ToDataStream(),
AudioBytes = (int)soundStream.Length,
Flags = SharpDX.XAudio2.BufferFlags.EndOfStream
};
//Make voice wrapper
VoiceChannel voice = new VoiceChannel(source, engine, MakeOutputMatrix());
//Volume
source.SetVolume((float)vol);
//Play sound
source.SubmitSourceBuffer(audioBuffer, soundStream.DecodedPacketsInfo);
source.Start();
sourceVoices.Add(voice);
return voice;
}
Following this implementation now lets me play up to 20 sound effects - but NOT because we are playing from the soundbank. Infact, even running the old method for sound effects now gets up to 20 sfx instances.
This has improved up to 20 because we have done NativeFile.ReadAllBytes(curdir + #"\Assets\Base.wav") in the constructor for the SoundBank.
I suspect NativeFile is holding a store of loaded file data, so you regardless of whether you run the original SoundEngine.AddAndPlaySFX() or SoundEngine.AddAndPlaySFXFromStore(), they are both running from memory?
Either way, this has quadrupled the limit from before, so this has been incredibly useful - but requires further work.
I would like to measure how much of system's memory is available in my C# code. I believe it is done something like this:
PerformanceCounter ramCounter = new PerformanceCounter(
"Memory"
, "Available MBytes"
, true
);
float availbleRam = ramCounter.NextValue();
The thing is Mono has no "Memmory" category. I iterated over the list of categories like this:
PerformanceCounterCategory[] cats = PerformanceCounterCategory.GetCategories();
string res = "";
foreach (PerformanceCounterCategory c in cats)
{
res += c.CategoryName + Environment.NewLine;
}
return res;
And closest category I found is "Mono Memory" which has no "Available MBytes" and keeps returning 0 on NextValue calls. Here's the complete list of categories mono returns:
Processor
Process
Mono Memory
ASP.NET
.NET CLR JIT
.NET CLR Exceptions
.NET CLR Memory
.NET CLR Remoting
.NET CLR Loading
.NET CLR LocksAndThreads
.NET CLR Interop
.NET CLR Security
Mono Threadpool
Network Interface
So does anyone know a way to measure the available memory in C# + Mono + Ubuntu?
[UPDATE]
I managed to do this in Ubuntu like this (using the external program free):
long GetFreeMemorySize()
{
Regex ram_regex = new Regex(#"[^\s]+\s+\d+\s+(\d+)$");
ProcessStartInfo ram_psi = new ProcessStartInfo("free");
ram_psi.RedirectStandardOutput = true;
ram_psi.RedirectStandardError = true;
ram_psi.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
ram_psi.UseShellExecute = false;
System.Diagnostics.Process free = System.Diagnostics.Process.Start(ram_psi);
using (System.IO.StreamReader myOutput = free.StandardOutput)
{
string output = myOutput.ReadToEnd();
string[] lines = output.Split(new string[] { Environment.NewLine }, StringSplitOptions.None);
lines[2] = lines[2].Trim();
Match match = ram_regex.Match(lines[2]);
if (match.Success)
{
try
{
return Convert.ToInt64(match.Groups[1].Value);
}
catch (Exception)
{
return 0L;
}
}
else
{
return 0L;
}
}
}
But the problem with this solution is that it works with Mono only if it is run within a Linux system. I would like to know if anyone can come up with a solution for Mono + Windows?
I realize this is an old question, but my answer might help someone.
You need to find out what performance categories and counters are available on your system.
foreach (var cat in PerformanceCounterCategory.GetCategories())
{
Console.WriteLine(cat.CategoryName + ":");
foreach (var co in cat.GetCounters())
{
Console.WriteLine(co.CounterName);
}
}
Then you will need to use the corresponding values for measuring the performance.
For Windows, those should be:
var memory = new PerformanceCounter("Memory", "Available MBytes");
For Linux (tested on Yogurt 0.2.3):
var memory = new PerformanceCounter("Mono Memory", "Available Physical Memory");
The values might be different depending on the operating system, but you will find the correct values by iterating the categories and the counters for each category.
Thread.Sleep(1000);
Place that after your first "NextValue" call (you can toss the very first NextValue return value) and follow it with your line:
float availbleRam = ramCounter.NextValue();
Performance counters need time to measure before they can return results.
Confirmed this works in Windows with .net 4.5, unsure about Linux with Mono.
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
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.
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.
For a programming project I would like to access the temperature readings from my CPU and GPUs. I will be using C#. From various forums I get the impression that there is specific information and developer resources you need in order to access that information for various boards. I have a MSI NF750-G55 board. MSI's website does not have any of the information I am looking for. I tried their tech support and the rep I spoke with stated they do not have any such information. There must be a way to obtain that info.
Any thoughts?
For at least the CPU side of things, you could use WMI.
The namespace\object is root\WMI, MSAcpi_ThermalZoneTemperature
Sample Code:
ManagementObjectSearcher searcher =
new ManagementObjectSearcher("root\\WMI",
"SELECT * FROM MSAcpi_ThermalZoneTemperature");
ManagementObjectCollection collection =
searcher.Get();
foreach(ManagementBaseObject tempObject in collection)
{
Console.WriteLine(tempObject["CurrentTemperature"].ToString());
}
That will give you the temperature in a raw format. You have to convert from there:
kelvin = raw / 10;
celsius = (raw / 10) - 273.15;
fahrenheit = ((raw / 10) - 273.15) * 9 / 5 + 32;
The best way to go for hardware related coding on windows is by using WMI which is a Code Creator tool from Microsoft, the tool will create the code for you based on what you are looking for in hardware related data and what .Net language you want to use.
The supported langauges currently are: C#, Visual Basic, VB Script.
Note that MSAcpi_ThermalZoneTemperature does not give you the temperature of the CPU but rather the temperature of the motherboard. Also, note that most motherboards do not implement this via WMI.
You can give the Open Hardware Monitor a go, although it lacks support for the latest processors.
internal sealed class CpuTemperatureReader : IDisposable
{
private readonly Computer _computer;
public CpuTemperatureReader()
{
_computer = new Computer { CPUEnabled = true };
_computer.Open();
}
public IReadOnlyDictionary<string, float> GetTemperaturesInCelsius()
{
var coreAndTemperature = new Dictionary<string, float>();
foreach (var hardware in _computer.Hardware)
{
hardware.Update(); //use hardware.Name to get CPU model
foreach (var sensor in hardware.Sensors)
{
if (sensor.SensorType == SensorType.Temperature && sensor.Value.HasValue)
coreAndTemperature.Add(sensor.Name, sensor.Value.Value);
}
}
return coreAndTemperature;
}
public void Dispose()
{
try
{
_computer.Close();
}
catch (Exception)
{
//ignore closing errors
}
}
}
Download the zip from the official source, extract and add a reference to OpenHardwareMonitorLib.dll in your project.