none
一个无法捕获ADO.NET Dataset的内存错误 RRS feed

  • 常规讨论

  • Dataset是ADO.NET在内存保存数据所用的新结构。在某些方面上,Dataset和ADO的Recordset对象相似;不过,Dataset可以把整个schema(包括table、关系、关键字连同真实数据)保存在内存中,在这一点上,Dataset比Recordset功能更强。因此,你可以查询和修改Dataset而不必担心影响到正在使用它的数据库。

    当考虑到Dataset把它的全部数据放在内存中,有些人会担心如果内存耗尽了会出现什么问题。做一个合理的猜测很容易,但是实际情况可能要比你猜测的要复杂一些。为了演示一下.NET在这种情况下会有什么动作,我先说说如何建立一个不停向一个Dataset中载入数据直到内存耗尽的项目。注意我们并不推荐这个过程,只是向你证明耗尽内存是多么容易的一件事。

    建立TooMuchData项目

    建立一个不停地向一个Dataset载入数据的项目很容易。打开Visual Studio .NET并创建一个新的VB.NET窗口应用程序。向视窗(form)中添加一个按钮控件并双击它,这样就开打了它的代码窗口。在代码窗口中填写下列代码:

    Private Sub Button1_Click(ByVal sender As System.Object, _
    ByVal e As System.EventArgs) Handles Button1.Click
    Dim sConnString As String = "Server=localhost;Database=pubs;uid=sa;pwd=;"
    Dim sSQL As String = "SELECT * FROM authors"
    Dim daProduct As SqlDataAdapter = New SqlDataAdapter(sSQL, sConnString)
    Dim myDS As New DataSet()
    Do While True
    daProduct.Fill(myDS, "authors")
    If myDS.Tables("authors").Rows.Count Mod 100 = 0 Then
    Debug.WriteLine(myDS.Tables("authors").Rows.Count.ToString())
    End If
    Loop
    End Sub

    代码的第三行建立字符串到变量sConnString的联接。如果你想重新建立这个项目,你或许需要修改这一行的代码,除非你在服务器上运行它并有一个"sa/no password"用户ID和相应的口令。

    代码的第四行建立一个简单的SQL查询。一般不推荐Select *这样的用法,但是这里没有什么问题,因为我们的目标就是抓到尽可能多的记录。选择的表格(table)的首要(primary)关键字域也不是问题,因为我们创建的Dataset是weakly type而且用来载入数据的DataTable的首要关键字域也没有设置。

    下两行代码创建DataAdapter和Dataset对象。然后进入一个死循环,在死循环中调用DataAdapter的Fill方法并把记录加入称之为“authors”的DataTable中。循环包括一个If语句用来显示行数的当前值是否可以被100整除。这不是不必可少的,但是它可以有两个方面的作用:首先,你可以知道程序依然在运行;其次,你可以知道大概有多少个记录加到Dataset中去了。

    运行本程序

    当你建立本程序后,你可能希望在运行它之前作些修改。当Dataset越来越大时,它将消耗越来越多的内存。一旦它耗尽所有可用内存,Windows就开始把内存交换到内存交换文件中。在许多机器上,交换文件是相当大的,所以本程序可能要运行好几个小时。例如,我在测试本程序时,用的是600-MHz PIII CPU和512-MB RAM的笔记本电脑。页交换文件设置为最小值以缩短程序运行时间——这是尽快完成测试的诀窍。即使这样,本程序在塞满所有的可用内存之前还是运行了几十分钟或者若干小时。

    现在你可以运行本程序并点击按钮控件来开始处理过程。它建立了一个到服务器的联接,从表格中读取数据并传到Dataset中的DataTable对象。同样的记录也保存到该Dataset直到内存最终耗尽。你可以通过任务管理器来观察内存的使用情况,你甚至可以看到可用内存的随着内存和磁盘的数据交换而增减的情况。在我的机器中,Dataset在机器耗尽内存前已经长到1400000条记录的规模。

    耗尽内存

    一旦你耗尽内存后会发生什么取决你是在VS.NET环境下运行本程序还是运行本程序的编译版。如果你运行的是本程序的编译版并且没有进行错误处理(error handling),你在程序运行中不会发现错误,它仅仅是在内存耗尽时停止运行。如果你在VS.NET环境下运行代码并且没有进行错误处理,程序将会停止运行并在调试窗口下出现下面的错误信息:

    Fatal out of memory error.
    The program '[2340] TooMuchData.exe' has exited with code 0 (0x0).

    因此,你可能会开始尝试添加一个错误处理函数来检测System.OutOfMemoryException的情况。例如,你可能用一个Try…Catch语句来观察是否出现例外。一个比较通用的方法如下:

    Do While True
    Try
    daProduct.Fill(myDS, "authors")
    If myDS.Tables("authors").Rows.Count Mod 100 = 0 Then
    Debug.WriteLine(myDS.Tables("authors").Rows.Count.ToString())
    End If
    Catch ex As Exception
    MessageBox.Show("Error: " & ex.Message)
    End Try
    Loop

    不幸的是,这种方法根本就不工作。在MessageBox.Show语句上设置一个断点,理论上当运行到这个语句上应该出现,但是这一点永远也不会到达。当最终耗尽内存时,调试窗口出现的却是下面的信息:

    Fatal out of memory error.
    An unhandled exception of type 'System.OutOfMemoryException' occurred in system.data.dll

    如果是MessageBox出现该消息情况会好些,但是相反,VS.NET(或者Framework)产生并显示了上述消息,跟踪这个特定的错误并不是有效的解决方法。

    如果在本程序中添加错误处理并编译运行它,那么你将得到另外一种结果。这次,你会发现MessageBox报告说程序遇到了一个无法处理的错误,类型为System.OutOfMemoryException,位于system.data.dll。然而,这个MessageBox来自Framework而不是你自己用代码编写的MessageBox

    并不象听起来那么容易

    你可以建立一个可以消耗所有可用内存的Dataset,但是消耗的过程并不简单,它需要大量的记录,尤其是大量的时间。Dataset可能需要几个小时才能填满内存,几乎没有什么应用程序可以在这种情况下运行很长时间,这就产生了问题。当然,每台机器的配置都不尽相同,如处理器速度、内存容量以及交换文件大小,但是这种结果的出现都是不受欢迎的。

    无论这种结果是如何令人讨厌,错误都应该可以被捕获。不幸的是,我们在这种情况下无法捕获到System.OutOfMemoryException错误,也就是说我们在应用程序中很难处理这种特定错误。

    2009年5月31日 7:02