Skip to content

Instantly share code, notes, and snippets.

@suchja
Last active June 2, 2017 11:20
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save suchja/16ff3d4d37e37bed9b39389d9ff6fa97 to your computer and use it in GitHub Desktop.
Save suchja/16ff3d4d37e37bed9b39389d9ff6fa97 to your computer and use it in GitHub Desktop.
DB-Interaktion mit zusätzlichem Timeout

Verwendung von TPL (Async / Await) zum Abbrechen von DB-Aktionen

Ausgangspunkt

Der Ausgangspunkt für dieses Projekt war die Anfrage wie eine Methode, die verschiedene Interaktionen mit einer Datenbank abwickelt nach einer definierten Zeit abgebrochen werden kann. Offensichtlich war in diesem konkreten Fall der Server nicht erreichbar oder wurde während der Kommunikation beendet.

Ganz allgemein stellen die verschiedenen Datenbank-Klassen (in diesem konkreten Beispiel wurde OdbcConnection und OdbcCommand verwendet) ein eigenes Timeout-Handling zur Verfügung. Ich denke, dass es auch Sinn macht die bereitgestellten Mechanismen der Datenbank-Klassen zu verwenden anstatt einen zusätzliches Timeout zu realisieren. Da momentan jedoch Zeit und Möglichkeit fehlen eine umfangreichere Fehlersuche zu machen, verwende ich dieses Projekt zum testen wie eine mögliche Lösung mit TPL (Task Parallel Library) aussehen könnte.

TPL & Async / Await

Eine Möglichkeit ein zusätzliches Timeout (unabhängig von den Odbc Klassen) zu realisieren ist die Auslagerung der Interaktion mit der Datenbank in einen separaten Task. Mit Async, Await und der TPL ist das relativ einfach möglich. Ein impliziter Vorteil ist, dass die Anwendung nicht mehr "einfriert" bei Datenbankabfragen.

Ein Risiko ist, dass die Anwendung durch die Nebenläufigkeiten komplexer wird und eine solche Änderung weitere Umbauten nach sich ziehen kann.

Variante 1 - CancellationToken

Der übliche und empfohlene Weg einen Task nach einer gewissen Zeit zu beenden ist die Verwendung eines CancellationToken.

Eine solche Lösung könnte wie folgt aussehen:

Imports System.Data.SqlClient
Imports System.Threading

Module Module1

    Sub Main()
        Using conn As New SqlConnection("Data Source=(local); Initial Catalog=NorthWind; Integrated Security=SSPI")
            Dim cmd As SqlCommand = New SqlCommand("select top 2 * from orders", conn)

            Dim result = OpenAndQueryAsync(conn, cmd).Result
            Console.WriteLine("Result from async Connection: " + result)

            If conn.State = ConnectionState.Open Then
                Dim reader As SqlDataReader = cmd.ExecuteReader()
                While reader.Read()
                    Console.WriteLine(String.Format("{0}", reader(0)))
                End While
            End If
        End Using

        Console.ReadLine()
    End Sub

    Async Function OpenAndQueryAsync(conn As SqlConnection, cmd As SqlCommand) As Task(Of String)
        Dim cts As CancellationTokenSource = New CancellationTokenSource(10000)
        Try
            Await conn.OpenAsync(cts.Token)
            Await cmd.ExecuteNonQueryAsync(cts.Token)
        Catch tcex As TaskCanceledException
            Return "Timeout"
        Finally
            cts.Dispose()
        End Try

        Return "Success"
    End Function

End Module

TBD: Vor- und Nachteile analysieren und beschreiben

Variante 2 - Task.Wait( Timeout )

Da in diesem konkreten Fall die nebenläufige Ausführung nicht unbedingt benötigt wird, kann auch die Task.Wait Methode verwendet werden. Diese hat eine Überladung bei der es möglich ist ein Timeout anzugeben:

Imports System.Data.SqlClient
Imports System.Threading

Module Module1

    Sub Main()
        Using conn As New SqlConnection("Data Source=(local); Initial Catalog=NorthWind; Integrated Security=SSPI")
            Dim cmd As SqlCommand = New SqlCommand("select top 2 * from orders", conn)

            Dim isTaskCompleted = OpenAndQueryAsync(conn, cmd).Wait(TimeSpan.FromSeconds(5))

            If isTaskCompleted Then
                Console.WriteLine("Connection is now open")
            Else
                Console.WriteLine("Timeout while connecting")
            End If

            If conn.State = ConnectionState.Open Then
                Dim reader As SqlDataReader = cmd.ExecuteReader()
                While reader.Read()
                    Console.WriteLine(String.Format("{0}", reader(0)))
                End While
            End If
        End Using

        Console.ReadLine()
    End Sub

    Async Function OpenAndQueryAsync(conn As SqlConnection, cmd As SqlCommand) As Task
        Await conn.OpenAsync()
        Await cmd.ExecuteNonQueryAsync()
    End Function

End Module
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment