Ressourcenprobleme mit Recordsets

In letzter Zeit werde ich wieder häufiger von Entwicklerkollegen angesprochen, die Fehler wie “Nicht genügend Systemressourcen” oder “Nicht genügend Speicherplatz zum Ausführen der Operation” erhalten, wenn sie mehrere Formulare mit Daten öffnen oder Code ausführen, der mit Recordsets arbeitet. Wenn sie dann über den Taskmanager die Ressourcen des Systems betrachten, stellen sie fest, dass es hier keinerlei Engpässe gibt. Wir schauen uns in diesem Beitrag an, wie dieser Fehler entstehen kann und welche Möglichkeiten es gibt, diesen zu beheben.

Fehlermeldungen zu erhalten, die nicht unmittelbar mit dem geschriebenen Code und eventuellen Syntax- oder Kompilierfehlern zusammenhängen, ist selten schön – erst recht nicht, wenn diese Fehler auf dem einen Rechner auftreten, auf dem anderen aber nicht oder zu anderen Zeitpunkten.

Wir schauen uns in diesem Beitrag einmal an, wie wir die beiden eingangs genannten Fehler reproduzieren können. Anschließend kümmern wir uns um mögliche Lösungen für diese Fehler.

Fehler “Nicht genügend Systemressourcen” reproduzieren

Um diesen Fehler zu reproduzieren, erzeugen wir eine ausreichend große Menge von Recordset-Objekten. Um diese ohne viel Aufwand im Speicher zu halten, definieren wir ein Klassenmodul, das lediglich den Verweis auf das geöffnete Recordset-Objekt aufnehmen soll. Der Code dieses Klassenmoduls, das wir clsRecordsetWrapper nennen wollen, sieht wie folgt aus:

Private m_Recordset As Recordset
Public Property Set Recordset(rst As DAO.Recordset)
     Set m_Recordset = rst
End Property

Es enthält also lediglich eine private Variable, die den Verweis auf ein Recordset-Objekt speichern soll. Um dieses an die Klasse zu übermitteln, haben wir dieser außerdem die Property Set-Prozedur Recordset zugewiesen, der wir als Parameter das zu speichernde Recordset übergeben.

Diese Prozedur (siehe Listing 1) deklariert eine Collection-Variable zum Speichern der Instanzen der Wrapper-Klasse, Variablen zum Speichern eines Verweises auf das Database-Objekt der aktuellen Datenbank und des zu öffnenden Recordsets sowie für eine Instanz der Klasse clsRecordsetwrapper. Außerdem instanziiert die Prozedur ein neues Collection-Objekt und referenziert es mit der Variablen col.

Public Sub Recordsets()
     Dim col As Collection
     Dim i As Long
     Dim db As DAO.Database
     Dim rst As DAO.Recordset
     Dim objRecordsetWrapper As clsRecordsetwrapper
     Set db = CurrentDb
     Set col = New Collection
     For i = 1 To 10000
         Set objRecordsetWrapper = New clsRecordsetwrapper
         With objRecordsetWrapper
             On Error Resume Next
             Set .Recordset = db.OpenRecordset("tblVieleKunden", dbOpenDynaset)
             If Not Err.Number = 0 Then
                 MsgBox "Fehler: " & Err.Number & vbCrLf & vbCrLf & "Beschreibung: " & Err.Description _
                     & vbCrLf & vbCrLf & "Anzahl Recordsets: " & i
                 Exit Sub
             End If
             On Error GoTo 0
         End With
         col.Add objRecordsetWrapper
         Debug.Print i
     Next i
End Sub

Listing 1: Prozedur zum Erstellen einiger Recordsets und zum Speichern dieser in einer Wrapper-Klasse inklusive Fehlerbehandlung

Die folgende For…Next-Schleife wird maximal 10.000 Mal durchlaufen. Es ist allerdings davon auszugehen, dass unter der Standardkonfiguration normale Rechner nach ein paar hundert Iterationen aufgeben. Innerhalb der For…Next-Schleife erstellen wir ein neues Objekt auf Basis der Klasse clsRecordsetwrapper und weisen es der Variablen objRecordsetWrapper zu.

Dann weisen wir bei deaktivierter Fehlerbehandlung der Eigenschaft Recordsets dieser Klasse ein mit OpenRecordset neu geöffnetes Recordset auf Basis einer Beispieltabelle zu. Dies löst ab einer bestimmten Menge erstellter Recordsets den oben genannten Fehler aus. Ist das der Fall (oder tritt ein anderer Fehler auf), wollen wir diesen in einer Meldung ausgeben. Neben der Fehlernummer und der Fehlermeldung gibt die Meldung noch die Anzahl der geöffneten Recordsets aus. Nach der Anzeige der Fehlermeldung wird die Prozedur mit Exit Sub beendet.

Damit die Recordsets in der Wrapper-Klasse im Speicher verweilen und so erst dafür sorgen, dass der Fehler ausgelöst wird, fügen wir diese mit der Add-Methode zu der Collection namens col hinzu.

Rufen wir diese Prozedur nun auf, erscheint nach einigen bis etlichen Durchläufen der Schleife die Fehlermeldung aus Bild 1 (hier haben wir die Prozedur von einer Schaltfläche aus aufgerufen). Mit einem Klick auf OK wird die Prozedur beendet und die durch die Recordset-Variablen gebundenen Ressourcen werden wieder freigegeben. Der Fehler ist reproduzierbar und zeigt, dass in Abhängigkeit von der aktuellen Konfiguration des Systems und vermutlich auch abhängig von der aktuellen Auslastung mehr oder weniger Recordset-Objekte erstellt werden können.

Anzeige der Fehlermeldung nach dem Öffnen von 305 Recordsets

Bild 1: Anzeige der Fehlermeldung nach dem Öffnen von 305 Recordsets

Fehler “Nicht genügend Speicherplatz zum Ausführen der Operation” reproduzieren

Diesen Fehler konnten wir reproduzieren, indem wir einige Formularinstanzen aufgerufen haben. Da wir keine Lust hatten, viele verschiedene Formulare zu erstellen und diese zu öffnen, haben wir zu Beispielzwecken von einer Technik Gebrauch gemacht, die sonst zur Anzeige mehrerer Instanzen von Formularen mit verschiedenen Datensätzen verwendet wird.

Das Formular ist ein einfaches Formular, das wir über die Eigenschaft Datensatzquelle an die Tabelle tblVieleKunden gebunden haben (siehe Bild 2). Außerdem haben wir die Eigenschaft Enthält Modul im Bereich Andere des Eigenschaftenblatts auf Ja eingestellt. Das ist nötig, damit wir mehrere Instanzen des Formulars erstellen können.

Das an eine Tabelle gebundene Formular

Bild 2: Das an eine Tabelle gebundene Formular

Die Prozedur finden Sie in Listing 2. Hier deklarieren wir eine Variable für ein Collection-Objekt und eines zum Erfassen des aktuellen Formulars. Dabei verwenden wir gleich den Typ des Klassenmoduls dieses Formulars, in diesem Fall Form_frmRecordset.

Public Sub VieleFormulareMitRecordsetOeffnen()
     Dim col As Collection
     Dim i As Long
     Dim frm As Form_frmRecordset
     Set col = New Collection
     For i = 1 To 10000
         On Error Resume Next
         Set frm = New Form_frmRecordset
         If Not Err.Number = 0 Then
             MsgBox "Fehler: " & Err.Number & vbCrLf & vbCrLf & "Beschreibung: " & Err.Description _
                 & vbCrLf & vbCrLf & "Anzahl Formulare: " & i
             Exit Sub
         End If
         On Error GoTo 0
         col.Add frm
         Debug.Print i
     Next i
End Sub

[

Listing 2: Prozedur zum Erstellen mehrerer Formularinstanzen mit Bindung an eine Tabelle

Danach instanziieren wir die Collection und starten in eine For…Next-Schleife, wie wir wieder bis zu 10.000 Mal durchlaufen. Das sollte reichen, um auch die beste Konfiguration in die Knie zu zwingen. Innerhalb der Schleife deaktivieren wir direkt die eingebaute Fehlerbehandlung. In der folgenden Anweisung weisen wir der Variablen frm eine neue Instanz der Formularklasse Form_frmRecordset zu.

Ist dabei ein Fehler aufgetreten, wird dieser innerhalb der folgenden If…Then-Bedingung mithilfe eines Meldungsfensters ausgegeben. Auch hier geben wir wieder die Fehlernummer, die Fehlerbeschreibung und die Anzahl der bisher geöffneten Formulare aus (siehe Bild 3).

Fehlermeldung bei zu vielen per Formular geöffneten Recordsets

Bild 3: Fehlermeldung bei zu vielen per Formular geöffneten Recordsets

Sollte beim Erstellen der aktuellen Formularinstanz kein Fehler aufgetreten sein, fügen wir die Variable frm mit dem Verweis auf diese Instanz der Collection col hinzu und laufen mit der For…Next-Schleife in den nächsten Durchgang.

Wir könnten die geöffneten Formulare auch noch anzeigen, aber das sorgt lediglich dafür, dass der Vorgang mehr Zeit in Anspruch nimmt.

Den gleichen Vorgang haben wir noch mit einem weiteren Formular reproduziert, das diesmal nicht selbst an die Tabelle tblVieleKunden gebunden war, sondern ein Kombinationsfeld mit dieser Tabelle als Datensatzquelle enthielt. Hier haben wir den gleichen Fehler erhalten wie beim gebundenen Formular.

Behandlung des Fehlers in Formularen

In den beiden vorherigen Beispielen haben wir den Fehler jeweils per VBA ausgelöst, sodass wir eine Idee bekommen konnten, woran das Problem ungefähr gelegen hat. Das ist ganz anders, wenn der Benutzer nur wenige Formulare öffnet, die aber sehr viele Recordsets nutzen – für die eigene Datensatzquelle, als Datensatzquelle für Unterformulare oder als Datensatzherkunft für Kombinationsfelder und Listenfelder.

Öffnet er eines dieser Formulare zu viel, erhält er gegebenenfalls noch nicht einmal einen VBA-Fehler, sondern die Meldung aus Bild 4.

Fehlermeldung beim Auslösen durch das Öffnen eines Formulars ohne VBA-Beteiligung

Bild 4: Fehlermeldung beim Auslösen durch das Öffnen eines Formulars ohne VBA-Beteiligung

Mit dieser können unerfahrene Benutzer und Entwickler nur wenig anfangen, zumal dieser Fehler je nach Auslastung des Systems mal auftritt und mal nicht.

Wenn Sie diesen Fehler reproduzieren wollen, empfehle ich den Aufruf der Prozeduren aus dem Modul mdlRecordset_InSpeichern. Hier haben wir aus der Prozedur VieleFormulareMitRecordsetOeffnen, die wir weiter oben bereits vorgestellt haben, die Deklaration der Collection-Variablen col und das Instanziieren der Collection ausgegliedert. Die Variable haben wir als öffentliche Variable deklariert, damit diese nach dem Durchlaufen der Prozedur nicht gelöscht wird. Das Instanziieren haben wir in die Prozedur SetCollection ausgelagert, damit wir dies separat erledigen können. Zum Reproduzieren rufen Sie nacheinander die Prozeduren SetCollection und VieleFormulareMitRecordsetOeffnen auf.

Letztere führt irgendwann zum Auftreten des obligatorischen Fehlers und damit dürfte der verfügbare Speicher annähernd ausgereizt sein. Wenn Sie nun manuell nochmals das Formular frmRecordset per Doppelklick auf den entsprechenden Eintrag im Navigationsbereich öffnen, könnte der Fehler bereits erneut ausgelöst werden.

Ende des frei verfügbaren Teil. Wenn Du mehr lesen möchtest, hole Dir ...

den kompletten Artikel im PDF-Format mit Beispieldatenbank

diesen und alle anderen Artikel mit dem Jahresabo

Schreibe einen Kommentar