2015-06-08 1 views
0

Я написал серверное приложение в 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() 

Любые советы/помощь по этому вопросу было бы весьма признателен, так что спасибо заранее! Если есть другой код/​​данные, которые вы хотели бы просто спросить.

+0

Одна плохая вещь, вложенные функции: 'Convert.ToInt32 (cmd.ExecuteScalar());' Что происходит, когда ExecuteScalar возвращает NULL? Кроме того, я бы использовал предложения 'using' или блок' finally', чтобы освободить ресурсы. –

+0

Что касается вложенных функций, я использую этот бит кода, когда я выполняю запрос SELECT COUNT (*), который всегда возвращает целое число> = 0, но я обязательно просмотрю свой код для опасных примеров этого. Я пробовал использовать «используемые» блоки безрезультатно. Правильно ли говорить, что используемый блок просто вызывает для вас распоряжение или есть еще больше? –

+0

Вы правы. Еще одна вещь, которую я узнал, когда сталкивался с проблемами памяти на C#, - это попытаться избежать ручного вызова GC или возиться с Windows API. Проблема, которую вы можете искать, может быть связана с фрагментацией LOH, поскольку вы используете массивы байтов, как и я. (см., например, http://stackoverflow.com/questions/686950/large-object-heap -фрагментация для другого вопроса, связанного с этой проблемой). Я решил использовать фиксированное количество предварительно распределенных байт-массивов, используемых во вращении. –

ответ

0

Возможно, вы столкнулись с проблемами фрагментации на большой куче объектов (другой интересный вопрос на SO: Large Arrays, and LOH Fragmentation. What is the accepted convention?, Large Object Heap Fragmentation). Проблема известна, и некоторые способы обхода были предоставлены в .NET fx 4.5 (например, http://dailydotnettips.com/2013/08/26/largeobjectheapcomapaction-in-net-garbage-collection/).

Что обычно делается для уменьшения воздействия (или полного его удаления), является использование выделенных пулов объектов, особенно в многопоточных приложениях. Вы заранее выделяете массивы достаточного размера и сохраняете переменную размера, которую вы увеличиваете, чтобы эффективно отслеживать размер используемой части вашего массива.

Если ваш код является однопоточным, я могу предложить вам заменить List<Alert> на Alert[] с достаточным размером и сохранить его в статическом состоянии. Добавьте переменную int, чтобы сохранить количество используемых элементов в массиве, например.

//Somewhere in the class 
static Alert[] alertList = new Alert[4096]; 
static int alertListSize = 0; 

//In your method 
alertListSize = 0; 
foreach (DataRow DR in DT.Rows) 
{ 
    var Alert = new Alert(); 
    ... //Data Collection 
    //REMOVED Alerts.Add(Alert); 
    alertList[alertListSize++] = Alert; 
} 

Обратите внимание, что вы не быть в состоянии использовать XmlSerializer на этом массиве, потому что это будет сериализовать все элементы (включая любые null из них), так что вам придется использовать что-то вроде XmlWriter. Поскольку вам приходится писать только ряд элементов, переход не должен быть сложным (хотя и не подходит для этого ответа).

Вы можете сделать то же самое с фиксированным размером byte[] для использования в MemoryStream, вы можете передать его конструктору. Не забудьте перемотать MemoryStream в начало буфера с Seek() после инициализации, поэтому XmlSerializer перезапишет что-либо в буфере с самого начала.

Кроме того, для дальнейшей помощи, пожалуйста, конвертируйте свой код с помощью операторов using, это позволяет сделать все более понятным (и убедитесь, что нет ошибок при вызове методов Dispose()).

Проблема с фрагментацией LOH является одним из вероятных кандидатов для явно необъяснимых ошибок из-за нехватки памяти в длинных программах на C#, обрабатывающих сериализацию (большие объекты), но может быть не единственным; может потребоваться дальнейшее расследование.

Смежные вопросы