Reputation: 155
I have a desktop application using visual studio 2012 C# and Firebird as database. When the program is being used for an hour without closing, there will be time that the Out of Memory Dialog box comes out and the program will no longer respond.
In my class, I am using Dispose() but I don't think I did it right because it gets the same result. Also the platform target is set to x64 but still gets the same result.
Here is an example of my class
using DevExpress.XtraEditors;
using FirebirdSql.Data.FirebirdClient;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace DepEdZDSMS.Class
{
class ClsAppointmntPic
{
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
FirebirdService.Close();
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public MemoryStream APP_FRONT { get; set; }
public MemoryStream APP_BACK { get; set; }
public DateTime EventTimestamp { get; set; } //for accomplished date and for log date
public void DeleteAppImage()
{
try
{
var del = new FbConnection(ClsConnectionImages.FirebirdSQL);
var fbcmd = new FbCommand("APP_DELETE", del);
fbcmd.Parameters.Add("@WORKXP_PK", FbDbType.Integer).Value = ClsEmployee.UpdateHandler2;
fbcmd.CommandType = CommandType.StoredProcedure;
fbcmd.Connection.Open();
fbcmd.ExecuteNonQuery();
fbcmd.Connection.Close();
}
catch (Exception errorcode)
{
XtraMessageBox.Show(String.Format("Error in connection: {0} Process failed.", errorcode.Message), @"Server Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
public void UpdateAppImage()
{
byte[] x = null; //front image
byte[] y = null; //back image
try
{
var adder = new FbConnection(ClsConnectionImages.FirebirdSQL);
var fbcmd = new FbCommand("APP_UPDATE", adder)
{
CommandType = CommandType.StoredProcedure
};
if (APP_FRONT != null) x = APP_FRONT.ToArray();
fbcmd.Parameters.Add("@APP_FRONT", FbDbType.Binary).Value = x;
if (APP_BACK != null) y = APP_BACK.ToArray();
fbcmd.Parameters.Add("@APP_BACK", FbDbType.Binary).Value = y;
fbcmd.Parameters.Add("@USER_PK", FbDbType.SmallInt).Value = ClsEmployee.USER_PK;
fbcmd.Parameters.Add("@APP_UPDATETIME", FbDbType.VarChar).Value = EventTimestamp;
fbcmd.Parameters.Add("@WORKXP_PK", FbDbType.VarChar).Value = ClsEmployee.UpdateHandler2;
fbcmd.Connection.Open();
fbcmd.ExecuteNonQuery();
fbcmd.Connection.Close();
}
catch (Exception errorcode)
{
XtraMessageBox.Show(String.Format("Error in connection: {0}. Saving failed.", errorcode.Message), @"Server Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
public void SaveAppImage() //122
{
byte[] x = null; //front image
byte[] y = null; //back image
try
{
var adder = new FbConnection(ClsConnectionImages.FirebirdSQL);
var fbcmd = new FbCommand("APP_INSERT", adder)
{
CommandType = CommandType.StoredProcedure
};
fbcmd.Parameters.Add("@WORKXP_PK", FbDbType.VarChar).Value = ClsEmployee.UpdateHandler2;
fbcmd.Parameters.Add("@EMP_PK", FbDbType.VarChar).Value = ClsEmployee.UpdateHandler;
if (APP_FRONT != null) x = APP_FRONT.ToArray();
fbcmd.Parameters.Add("@APP_FRONT", FbDbType.Binary).Value = x;
if (APP_BACK != null) y = APP_BACK.ToArray();
fbcmd.Parameters.Add("@APP_BACK", FbDbType.Binary).Value = y;
fbcmd.Parameters.Add("@USER_PK", FbDbType.SmallInt).Value = ClsEmployee.USER_PK;
fbcmd.Parameters.Add("@APP_UPDATETIME", FbDbType.VarChar).Value = EventTimestamp;
fbcmd.Connection.Open();
fbcmd.ExecuteNonQuery();
fbcmd.Connection.Close();
}
catch (Exception errorcode)
{
XtraMessageBox.Show(String.Format("Error in connection: {0}. Saving failed.", errorcode.Message), @"Server Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
public ClsAppointmntPic(int refID)
{
try
{
var app = new ClsAppointmntPic();
var x = new DataSet();
var y = new FbDataAdapter();
var f = new FbCommand("IMAGE_APPOINTMENT", FirebirdService);
f.Parameters.Add("@X", FbDbType.Integer).Value = refID;
f.CommandType = CommandType.StoredProcedure;
y.SelectCommand = f;
y.Fill(x, "APPOINTMENT");
if (x.Tables[0].Rows.Count > 0)
{
var fx = x.Tables[0].Rows[0];
if (!fx["APP_FRONT"].Equals(DBNull.Value))
{
var i = (byte[])fx["APP_FRONT"];
var fx2 = new MemoryStream(i);
APP_FRONT = fx2;
}
if (!fx["APP_BACK"].Equals(DBNull.Value))
{
var j = (byte[])fx["APP_BACK"];
var fx2 = new MemoryStream(j);
APP_BACK = fx2;
}
}
app.FirebirdService.Close();
}
catch (Exception e)
{
MessageBox.Show(@"Error: " + e.Message, @"DepEdZDSMS", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
public ClsAppointmntPic()
{
// TODO: Complete member initialization
}
private void Openconnection()
{
if (FirebirdService.State == ConnectionState.Open)
{
FirebirdService.Close();
}
FirebirdService.Open();
}
private void Closeconnection()
{
FirebirdService.Close();
}
private static readonly string Firebird = ClsConnectionImages.FirebirdSQL;
/// <summary>
///
/// </summary>
private readonly FbConnection FirebirdService = new FbConnection(Firebird);
}
}
Upvotes: 0
Views: 715
Reputation: 416049
Once more with feeling:
Dispose() has NOTHING to do with memory.
Memory is managed by the garbage collector. Dispose(
) is about unmanaged resources, like network sockets, file handles, database connections, and gdi resources.
An example of the correct use of Dispose()
is to modify your existing DeleteApp()
method like this:
public void DeleteAppImage()
{
try
{
using (var del = new FbConnection(ClsConnectionImages.FirebirdSQL))
using(var fbcmd = new FbCommand("APP_DELETE", del))
{
fbcmd.Parameters.Add("@WORKXP_PK", FbDbType.Integer).Value = ClsEmployee.UpdateHandler2;
fbcmd.CommandType = CommandType.StoredProcedure;
del.Open();
fbcmd.ExecuteNonQuery();
}
}
catch (Exception errorcode)
{
XtraMessageBox.Show(String.Format("Error in connection: {0} Process failed.", errorcode.Message), @"Server Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
}
Note that I never even called Dispose()
directly. I let the framework do it for via using blocks, which ensure that it will be called even if an exception is thrown. FWIW, I'd also tend to remove the error handling at this level; do that closer to the presentation tier, rather than the data tier.
Now on the memory issue. The problem has to do with lines like this:
if (APP_FRONT != null) x = APP_FRONT.ToArray();
That line takes a (potentially large-ish) memory stream and duplicates all of the memory in it to a byte array.
.Net uses a generational garbage collector, and it has a special generation called the Large Object Heap (LOH). Objects above a certain size automatically end up on the Large Object Heap (it used to be 85,000 bytes, may have changed in more recent versions). What makes this generation extra special is that it's not collected very often. Even when it's collected, it's almost never compacted.
You can think of compaction like defragmenting a hard drive. When an object is reclaimed, it leaves address space behind. The physical memory it used is returned to the operating system and is available to other processes, but the address space assigned to your process still has a hole. Compaction moves the remaining objects forward in memory to fill up those empty holes... except that the large object heap doesn't do this very often.
An application that has repeated allocations to the LOH will over time use up all of the address space available to its process. There is still plenty of memory available to the operating system, but the process can't request any more, because it has no way to assign it an address. At this point, you will see an OutOfMemory exception.
To fix the problem, you need to avoid these allocations... either find a way to re-use the same set of memory (ie: allocate ONE byte array buffer you can re-use when copying from the MemoryStreams), work with chunks small enough to avoid the LOH, or keep things in the stream instead of an array. New as of .Net 4.5.1, you can also tell the garbage collector to do a full compact via the LargeObjectHeapCompactionMode
property, and have a way for your service to set this periodically. But that's treating the symptom, rather than the disease.
Upvotes: 2
Reputation: 52280
You need to declare that your class implements IDisposable or else any using statement will not know how to find the Dispose method and won't release anything. Even if the method is there.
class ClsAppointmentPic : IDisposable
Upvotes: 0