DataAdapter.Fill上的System.Data.Odbc内存管理和AccessViolationException

时间:2015-06-08 13:25:58

标签: c# .net access-violation dataadapter

我在C#.NET framework 4.5中编写了一个服务器应用程序。它每30-120秒进入一个数据库,使用System.Data.Odbc命名空间中定义的工具获取必要数据的更新。数据存储在List<>中。我创建的容器类来存储所有必需的数据。 List是Xml Serialized并通过TCP发送到连接的客户端。应用程序通常会运行5-6个小时,然后通过AccessViolationException使其停止运行。调用OdbcDataAdapter.Fill()时似乎抛出了异常。继续,我遇到的主要问题是我的数据收集功能导致我的应用程序的工作集每次运行时增加大约4兆字节,有时它可以在2分钟内运行3次。数据收集过程非常繁忙,但简而言之。

编辑:我最近使用Scitech Memory Profiler对应用程序进行了分析。事实证明,托管字节数仅暂时增加到大约1MB,然后重置为大约400KB。因此,与我最初的想法相反,我的应用程序中可能没有内存泄漏。然而,工艺工作集的规模仍在迅速增加,对此的解释使我望而却步。我在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) {
             ...
         }
     }   

我不是百分之百确定导致这种记忆增长的原因。我已确保处理所有非托管资源,并在每次运行后调用GC.Collect。为了抑制ram增长,我调用了Windows 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()

对此事的任何建议/帮助将不胜感激,所以提前感谢!如果您希望看到任何其他代码/数据,请询问。

1 个答案:

答案 0 :(得分: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#程序中可能无法解释的内存不足错误的可能候选者之一,但可能不是唯一的;可能需要进一步调查。