Я написал серверное приложение в C# .NET framework 4.5. Он отправляется в базу данных каждые 30-120 секунд для получения обновлений необходимых данных с помощью инструментов, определенных в пространстве имен System.Data.Odbc. Данные хранятся в списке <> класса контейнера, который я создал для хранения всех необходимых данных. Список Xml сериализуется и отправляется подключенным клиентам с TCP. Приложение обычно работает в течение 5-6 часов, прежде чем оно будет прервано с помощью исключения AccessViolationException. Кажется, что исключение вызывается при вызове OdbcDataAdapter.Fill(). Продолжая, основная проблема, с которой я столкнулся, заключается в том, что моя функция сбора данных заставляет рабочий набор моего приложения увеличиваться примерно на 4 мегабайта каждый раз, когда он запускается, и время от времени он может работать 3 раза за 2 минуты. Процесс сбора данных бурный, но здесь он в двух словах.System.Data.Odbc Управление памятью и AccessViolationException в DataAdapter.Fill
EDIT: Недавно я профилировал приложение, используя Profit Profiler Profiler. Оказывается, количество управляемых байтов только временно увеличивается примерно до 1 МБ, а затем сбрасывается примерно до 400 КБ. Поэтому вопреки тому, что я изначально думал, в моем приложении не может быть утечек памяти. Тем не менее, размер рабочего процесса по-прежнему быстро растет, объяснение этого ускользает от меня. Я поставил точку останова в AccessViolationException и, надеюсь, сделав снимок памяти с этим профилировщиком, вы обнаружите причину.
Для начала, вот как выглядит мой контейнерный класс.
public class Alert
{
public enum OrderType
{
...
}
public enum AlertType
{
...
}
//All the members of these structs are managed
public struct Unreleased
{
...
}
public struct AlloData
{
...
}
public AlloData AllocationData { get; set; }
public Unreleased UnreleasedData { get; set; }
public string OrderNO { get; set; }
public string PickNO { get; set; }
public OrderType Type { get; set; }
public AlertType Code { get; set; }
public string Customer { get; set; }
public Int64 ElapsedSeconds { get; set; }
public BackOrderData BackOrderData { get; set; }
}
Вот функция, которая извлекает данные
private static XmlSerializer XMLS = new XmlSerializer(typeof(List<Alert>))
[System.Runtime.ExceptionServices.HandleProcessCorruptedStateExceptions()]
public Byte[] getAlerts()
{
try
{
OdbcConnection Conn = new OdbcConnection(SB.ConnectionString);
Conn.Open();
List<Alert> Alerts = new List<Alert>();
String Query = "...";
var cmd = new OdbcCommand(Query, Conn);
int cnt = Convert.ToInt32(cmd.ExecuteScalar());
//After I'm done with OdbcCommand/Data Adapter instances I dispose and null them
cmd.Dispose(); cmd = null;
Query = "...";
cmd = new OdbcCommand(Query, Conn);
cnt += Convert.ToInt32(cmd.ExecuteScalar());
cmd.Dispose(); cmd = null;
...
var DT = new DataTable();
var DA = new OdbcDataAdapter(Query, Conn);
DA.Fill(DT);
DA.Dispose(); DA = null;
... //A ton of data collection etc..
foreach (DataRow DR in DT.Rows)
{
var Alert = new Alert();
... //Data Collection
Alerts.Add(Alert);
}
DT.Dispose(); DT = null;
... //More
byte[] bytes = null;
MemoryStream MS = new MemoryStream();
XMLS.Serialize(MS, Alerts);
bytes = MS.ToArray();
MS.Dispose(); MS = null;
Alerts = null;
Conn.Close();
Conn.Dispose();
Conn = null;
return bytes;
catch(Exception ex) {
...
}
}
Я не 100% уверен, что вызывает этот рост памяти. Я позаботился об утилизации всех неуправляемых ресурсов, и я делаю вызов GC.Collect после каждого запуска. Для подавления роста барана я называю окно функции API SetProcessWorkingSetSize
[DllImport("kernel32.dll")]
public static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max);
//... After each data collection
GC.Collect();
GC.WaitForPendingFinalizers();
SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, -1, -1);
Я думаю, что если что-то есть шанс вызвать AccessViolationException
это такое поведение, однако это единственный способ, который я нашел, чтобы предотвратить OutOfMemoryException
происходят после того, как час выполнения.
Вот исключение:
System.AccessViolationException: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
at System.Data.Common.UnsafeNativeMethods.SQLExecDirectW(OdbcStatementHandle StatementHandle, String StatementText, Int32 TextLength)
at System.Data.Odbc.OdbcStatementHandle.ExecuteDirect(String commandText)
at System.Data.Odbc.OdbcCommand.ExecuteReaderObject(CommandBehavior behavior, String method, Boolean needReader, Object[] methodArguments, SQL_API odbcApiMethod)
at System.Data.Odbc.OdbcCommand.ExecuteReaderObject(CommandBehavior behavior, String method, Boolean needReader)
at System.Data.Odbc.OdbcCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.Odbc.OdbcCommand.ExecuteDbDataReader(CommandBehavior behavior)
at System.Data.Common.DbCommand.System.Data.IDbCommand.ExecuteReader(CommandBehavior behavior)
at System.Data.Common.DbDataAdapter.FillInternal(DataSet dataset, DataTable[] datatables, Int32 startRecord, Int32 maxRecords, String srcTable, IDbCommand command, CommandBehavior behavior)
at System.Data.Common.DbDataAdapter.Fill(DataTable[] dataTables, Int32 startRecord, Int32 maxRecords, IDbCommand command, CommandBehavior behavior)
at System.Data.Common.DbDataAdapter.Fill(DataTable dataTable)
at PickWatchServer.DBSearch.getAlerts()
Любые советы/помощь по этому вопросу было бы весьма признателен, так что спасибо заранее! Если есть другой код/данные, которые вы хотели бы просто спросить.
Одна плохая вещь, вложенные функции: 'Convert.ToInt32 (cmd.ExecuteScalar());' Что происходит, когда ExecuteScalar возвращает NULL? Кроме того, я бы использовал предложения 'using' или блок' finally', чтобы освободить ресурсы. –
Что касается вложенных функций, я использую этот бит кода, когда я выполняю запрос SELECT COUNT (*), который всегда возвращает целое число> = 0, но я обязательно просмотрю свой код для опасных примеров этого. Я пробовал использовать «используемые» блоки безрезультатно. Правильно ли говорить, что используемый блок просто вызывает для вас распоряжение или есть еще больше? –
Вы правы. Еще одна вещь, которую я узнал, когда сталкивался с проблемами памяти на C#, - это попытаться избежать ручного вызова GC или возиться с Windows API. Проблема, которую вы можете искать, может быть связана с фрагментацией LOH, поскольку вы используете массивы байтов, как и я. (см., например, http://stackoverflow.com/questions/686950/large-object-heap -фрагментация для другого вопроса, связанного с этой проблемой). Я решил использовать фиксированное количество предварительно распределенных байт-массивов, используемых во вращении. –