24.4 Asynchrone Abfragen
Die Methoden ExecuteReader, ExecuteNonQuery oder ExecuteXmlReader blockieren die Anwendung, sodass der Programmfluss erst nach der Antwort des SQL Servers weitergeht. Dauert die Operation eine längere Zeit, wirkt die Clientanwendung wie eingefroren. Daher stellt ADO.NET außerdem asynchrone Methoden bereit, jedoch nur im SqlClient-Provider für SQL Server ab Version 7.0. Andere Provider kennen nur synchrone Abfragen.
Jede der drei Execute-Methoden erhält je eine Begin- und eine End-Methode. Für ExecuteReader sind es z. B. BeginExecuteReader und EndExecuteReader. In der folgenden Syntax sind optionale Parameter in eckige Klammern gesetzt:
Public Function BeginExecuteReader([beh As CommandBehavior]) As IAsyncResult Public Function BeginExecuteReader(call As AsyncCallback, state As Object [, beh As CommandBehavior]) As IAsyncResult Public Function EndExecuteReader(result As IAsyncResult) As SqlDataReader |
Mit BeginExecuteReader wird die asynchrone Operation gestartet. Der aufrufende Code initiiert die Aktion und fährt sogleich mit dem Programmfluss fort, ohne auf den Abschluss der Abfrage zu warten. Woher weiß das Clientprogramm, wann wie die Daten abgerufen werden können? Dazu gibt es zwei Möglichkeiten an, die ich in Beispielen vorstellen werde:
- Sie fragen in einer Schleife permanent ab, ob die asynchrone Operation bereits beendet ist. Dieses Verfahren wird als Polling bezeichnet.
- Sie definieren eine Rückrufmethode (Callback-Methode), die aufgerufen wird, sobald das Ergebnis vorliegt.
Asynchrone Operationen sind per Vorgabe nicht aktiviert. Damit das SqlConnection-Objekt auch asynchrone Abfragen ermöglicht, muss die Verbindungszeichenfolge um
Asynchronous Processing=true
ergänzt werden. Alternativ weisen Sie der Eigenschaft AsynchronousProcessing eines SqlConnectionStringBuilder-Objekts den Wert True zu.
Unsere bisherigen einfachen Abfragen sind zu schnell, um die Effekte asynchroner Operationen wahrzunehmen. Daher simulieren wir eine längere Abfrage mit der Anweisung WAITFOR DELAY, die den SQL Server um eine gewisse Zeit verzögert, zum Beispiel um zwei Sekunden:
WAITFOR DELAY '00:00:02'
24.4.1 Polling
Sehen wir uns zuerst den gesamten Beispielcode an:
'...\ADO\Datenbankabfragen\Polling.vb |
Option Strict On Imports System.Data.Common, System.Data.SqlClient Namespace ADO Module Polling Sub Test() Dim con As SqlConnection = New SqlConnection("Data Source=.;" & _ "Initial Catalog=Northwind;Integrated Security=sspi;" & _ "Asynchronous Processing=true") Dim cmd As SqlCommand = New SqlCommand( _ "WAITFOR DELAY '00:00:01';SELECT TOP 10 * FROM Products", con) con.Open() Dim result As IAsyncResult = cmd.BeginExecuteReader() ' asynchron While (Not result.IsCompleted) ' warten Arbeiten() End While Console.WriteLine(Environment.NewLine & "Und nun das Ergebnis:") Dim dr As SqlDataReader = cmd.EndExecuteReader(result) While dr.Read() Console.WriteLine(dr("ProductName")) End While dr.Close() : con.Close() : Console.ReadLine() End Sub Sub Arbeiten() Console.Write(DateTime.Now.Millisecond & " ") Threading.Thread.Sleep(50) End Sub End Module End Namespace
Beim Polling prüft der Client, ob die durch die Begin-Methode angestoßene Operation abgeschlossen ist. Die Kommunikation mit dem Thread, in dem die Begin-Methode läuft, findet über die Statuseigenschaft IsCompleted statt. Sie wird von dem DbAsynchResult-Objekt bereitgestellt, das die Begin-Methode als Ergebnis zurückliefert und das die Schnittstelle IAsyncResult implementiert. Der Status kann nur ausgelesen werden und ist während der Ausführung der Operation False. Beachten Sie, dass in diesem Beispiel die parameterlose Methode BeginExecuteReader eingesetzt wird. Dies hat zur Folge, dass die Operation sang- und klanglos endet, ohne uns automatisch, außer über den Status, über ihr Ende zu informieren.
Solange die Ergebnismenge noch nicht vorliegt, hat IsCompleted den Wert False und die Clientanwendung erledigt in Arbeiten eine andere Aufgabe und gibt einige Zahlen aus (Sleep simuliert eine ausgedehntere Aktivität). Ist die Anfrage an den Datenbankserver beendet, kann das Ergebnis geholt werden. Dazu dient die Methode EndExecuteReader, die ihrerseits die Referenz auf ein SqlDataReader-Objekt bereitstellt, das wir zur Ausgabe der Spalte ProductName benutzen.
24.4.2 Bereitstellen einer Rückrufmethode
Während beim Polling fortwährend der eigene Code prüft, ob der Datenbankserver die Anfrage bearbeitet hat, obliegt es durch das Bereitstellen einer Rückrufmethode der Begin-Methode, die Rückrufmethode nach getaner Arbeit aufzurufen.
Die Adresse der Rückrufmethode, die im folgenden Beispiel Rückruf heißt, wird dem ersten Parameter der überladenen Methode BeginExecuteReader übergeben. Die Rückrufmethode ist vom Typ des Delegates AsyncCallback, das einen Parameter vom Typ IAsyncResult hat und keinen Wert zurückgibt. Im zweiten Parameter erhält BeginExecuteReader ein beliebiges Objekt, auf das in der Rückrufmethode über die Eigenschaft AsyncState des Methodenparameters zugegriffen werden kann. In unserem Beispiel speichern wir das Kommando zur Erzeugung eines DataReaders und die Verbindung zum Aufräumen mit Close nach getaner Arbeit. Wie Sie die Informationen organisieren, bleibt Ihnen überlassen. Das Beispiel nutzt ein Array von Objekten.
Nach Beendigung der asynchronen Operation wird die Rückrufmethode ausgeführt, aus der heraus EndExecuteReader aufgerufen wird. Das dazu notwendige SqlCommand-Objekt steckt in dem Objekt, das dem zweiten Parameter der Methode BeginExecuteReader übergeben wurde, und ist in der Eigenschaft AsyncState des IAsyncResult-Parameters gespeichert. Da AsyncState vom Typ Object ist, müssen noch einige Konvertierungen mit CType erfolgen.
'...\ADO\Datenbankabfragen\Callback.vb |
Option Strict On Imports System.Data.Common, System.Data.SqlClient Namespace ADO Module Callback Sub Test() Dim con As SqlConnection = New SqlConnection("Data Source=.;" & _ "Initial Catalog=Northwind;Integrated Security=sspi;" & _ "Asynchronous Processing=true") Dim cmd As SqlCommand = New SqlCommand( _ "WAITFOR DELAY '00:00:01';SELECT TOP 3 * FROM Products", con) con.Open() Dim callback As New AsyncCallback(AddressOf Rückruf) Dim info() As Object = {cmd, con} cmd.BeginExecuteReader(callback, info) ' asynchron For i As Integer = 0 To 25 : Arbeiten() : Next Console.ReadLine() End Sub Sub Rückruf(ByVal result As IAsyncResult) Dim info() As Object = CType(result.AsyncState, Object()) Dim cmd As SqlCommand = CType(info(0), SqlCommand) Dim con As SqlConnection = CType(info(1), SqlConnection) Dim dr As SqlDataReader = cmd.EndExecuteReader(result) Console.WriteLine(Environment.NewLine & "Und nun das Ergebnis:") While dr.Read() : Console.WriteLine(dr("ProductName")) : End While dr.Close() : con.Close() End Sub Sub Arbeiten() Console.Write(DateTime.Now.Millisecond & " ") Threading.Thread.Sleep(50) End Sub End Module End Namespace
Die Ausgabe zeigt, wie die Hauptroutine durch die Rückrufmethode unterbrochen wird. Ohne dass der Hauptthread mit Sleep für den asynchronen Thread Platz macht, kann es sein, dass alle Arbeiten-Aufrufe vor der Ergebnisausgabe erfolgen.
144 204 254 304 354 405 455 505 555 605 655 705 755 805 855 905 955 5 55 106 Und nun das Ergebnis: Tee Chang Aniseed Syrup 156 206 256 306 356 406
Ihre Meinung
Wie hat Ihnen das Openbook gefallen? Wir freuen uns immer über Ihre Rückmeldung. Schreiben Sie uns gerne Ihr Feedback als E-Mail an kommunikation@rheinwerk-verlag.de.