Delphi-PRAXiS

Delphi-PRAXiS (https://www.delphipraxis.net/forum.php)
-   Datenbanken (https://www.delphipraxis.net/15-datenbanken/)
-   -   TSQLDataSet, TSQLConnection Multithread Exception (https://www.delphipraxis.net/187500-tsqldataset-tsqlconnection-multithread-exception.html)

Barthiboy 4. Dez 2015 07:58

Datenbank: MSSQL • Version: 2014 • Zugriff über: DB Express TSQLConnection

TSQLDataSet, TSQLConnection Multithread Exception
 
Hallo zusammen.
ich habe eine Anwendung geschrieben in der in einem Thread Daten via TCPIP von einem Server gelesen werden und in eine MSSQL Datenbank eingegraten werden.
Hierfür verwende ich TSQLDataSet, TSQLConnection
Beispiel:
Delphi-Quellcode:
  FSQLDataset.Close;
  FSQLDataset.CommandText := SQLCommand;
  FSQLDataset.ExecSQL(true);
funktioniert auch wunderbar.
Allerdings möchte ich jetzt im Main Thread (VCL) jetzt eine Anzahl der Datensätze aus der Tabelle ermitteln.
Hierfür habe ich wieder eine TSQLDataSet, TSQLConnection Komponente verwendet.
Somit eigentlich schön Threadsafe
Beispiel der abfrage
Delphi-Quellcode:
      SQLDS_Work.Close;
      SQLDS_Work.CommandText := 'SELECT COUNT(WareneingangsNr) from WEP02.dbo.WareneingangsNr WHERE Aktiv = 1';
      SQLDS_Work.Open;
      if SQLDS_Work.FieldCount > 0 then begin
        if SQLDS_Work.Fields[0].AsInteger > 1 then begin // Fehler weil mehrere Wareneingänge aktiv
          if Assigned(Logger) then begin
            Logger.LogErrorMessage('Es sind ' + IntToStr(SQLDS_Work.RecordCount) + ' Wareneingänge aktiv. Fehler bei Timer_Chart aufgetreten');
          end;
          Exit;
        end
und so weiter

wenn jeweils nur ein Thread läuft funktioniert es ohne Probleme.
Laufen beide knallts.
Aber warum?
Ich baue für jeden Thread eine extra Verbindung auf.
Eigentlich sollte eine MSSQL Datenbank doch so etwas können.
Es werden auch keine Objekte aus dem jeweils anderen Thread verwendet.

Danke für eure Hilfe

quaero 4. Dez 2015 08:04

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Zwei Datenverbindungen auf die gleiche Tabelle - davon ein schreibender Zugriff. Wird der lesende Zugriff da nicht ausgesperrt?

baumina 4. Dez 2015 08:05

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Zitat:

Zitat von Barthiboy (Beitrag 1323368)
Laufen beide knallts.

Darunter können wir uns jetzt echt viel vorstellen. Evtl. hast du dafür noch eine genauere Fehlerbeschreibung.

Barthiboy 4. Dez 2015 09:22

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Also ich bekomme eine Exception der Klasse $C00000005 mit der Meldung '$C00000005 ACCESS_VIOLATION'

Ich denke es gibt hier verschiedene Isolation Levels
https://msdn.microsoft.com/de-de/lib...=sql.120).aspx
Allerdings bei jeder Veränderung meinerseits, das gleiche Ergebnis

baumina 4. Dez 2015 09:37

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Mit solch spärlichen Informationen kann dir wahrscheinlich hier keiner helfen.

Barthiboy 4. Dez 2015 09:43

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
dann wirst DU wohl oder übel doch einfach mal eine Frage stellen müssen.

Hier noch die DBXConfig.ini
DriverName=MSSQL
SchemaOverride=%.dbo
DriverUnit=Data.DBXMSSQL
DriverPackageLoader=TDBXDynalinkDriverLoader,DBXCo mmonDriver190.bpl
DriverAssemblyLoader=Borland.Data.TDBXDynalinkDriv erLoader,Borland.Data.DbxCommonDriver,Version=19.0 .0.0,Culture=neutral,PublicKeyToken=91d62ebb5b0d1b 1b
MetaDataPackageLoader=TDBXMsSqlMetaDataCommandFact ory,DbxMSSQLDriver190.bpl
MetaDataAssemblyLoader=Borland.Data.TDBXMsSqlMetaD ataCommandFactory,Borland.Data.DbxMSSQLDriver,Vers ion=19.0.0.0,Culture=neutral,PublicKeyToken=91d62e bb5b0d1b1b
LibraryName=dbxmss.dll
VendorLib=sqlncli10.dll
VendorLibWin64=sqlncli10.dll
HostName=DE01PCSF0799\AUT_SQL_SERVER
Database=WEP02
MaxBlobSize=-1
LocaleCode=0000
IsolationLevel=DirtyRead
OSAuthentication=False
PrepareSQL=True
User_Name=
Password=
BlobSize=-1
ErrorResourceFile=
OS Authentication=False
Prepare SQL=False

baumina 4. Dez 2015 10:10

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Du könntest z.B. debuggen und sagen wo welche Fehlermeldung auftaucht, oder du könntest den Quellcode des Threads zeigen, wo z.B. wie synchronisiert wird. Eine Zugriffsverletzung ist meist ein Hinweis auf einen Zugriff auf ein Objekt, das bereits freigegeben wurde, dass die SQL-Statements das auslösen ist recht unwahrscheinlich.

Barthiboy 4. Dez 2015 10:30

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Na das is doch mal eine Ansage.
Mach ich doch gleich so gut ich kann.

ich habe jetzt mit einem try-except endlich eine genauere Fehlerbeschreibung:
04.12.2015 11:19:27 Client : Exception = Zugriffsverletzung bei Adresse 000000000BB4768C in Modul 'dbxmss.dll'. Lesen von Adresse FFFFFFFFFFFFFFFF
04.12.2015 11:20:16 Client : Exception = Zugriffsverletzung bei Adresse 000000000BB47680 in Modul 'dbxmss.dll'. Lesen von Adresse 0000000000000082

Der Thread sieht in etwa so aus

Delphi-Quellcode:
       
if DataCount > 0 then begin
          // Daten einlesen
          SendStr := 'Worksation;' + FormatDateTime('yyyy.mm.dd hh-nn-ss', now) + ';' + IntToStr(SessionID) + ';FetchDataToTransmit##';
          FIdTCPClient.IOHandler.WriteLn(SendStr, nil);

          for j := 0 to Datacount - 1 do begin
            if not KommEnable then begin
              // Wenn der Benutzer die Kommunikation pausiert, hier warten bis es weiter geht!
              FIdTCPClient.Disconnect;
              Logged := false;
              break;
            end;
            for i := 0 to 21 do begin
              Count      := FIdTCPClient.IOHandler.ReadLongInt(false);
              RecStr     := FIdTCPClient.IOHandler.ReadString(Count, nil);
              Logger.LogMessage('Server : ' + RecStr);
              if DataExport then begin
                SLStr      := SLStr + #9 + RecStr;
              end;
              Daten[i]   := RecStr;
              if i = 21 then begin
                if not DataNotInDB then begin // doppelte Verneinung (;
                  if Produkt_Auswerten(Daten) < 0 then begin
                    KommEnable := false;
                  end
                end;
              end;
              RemainCount := Datacount - j - 1;
              // Wenn nichts abzuholen ist
              if RemainCount < 0 then RemainCount := 0;
              // Ack senden
              SendStr    := '1';
              Logger.LogMessage('Client : ' + SendStr);
              FIdTCPClient.IOHandler.Write(SendStr, nil);
            end;
            // Einen Datensatz komplett gelesen
            if DataExport then begin
              SLDaten.Add(SLStr);
              SLStr := '';
            end;
            if Terminated then begin
              Break;
            end;
          end;
Und Das updaten der Anzeige im Main wird von einem Timer getriggert und sieht dann so aus

Delphi-Quellcode:
  if SQLC_Work.Connected then begin
    try
      SQLDS_Work.Close;
      // Aktuellen Wareneingang ermitteln
      SQLDS_Work.Close;
      SQLDS_Work.CommandText := 'SELECT COUNT(WareneingangsNr) from WEP02.dbo.WareneingangsNr WHERE Aktiv = 1';
      SQLDS_Work.Open;
      if SQLDS_Work.FieldCount > 0 then begin
        if SQLDS_Work.Fields[0].AsInteger > 1 then begin // Fehler weil mehrere Wareneingänge aktiv
          if Assigned(Logger) then begin
            Logger.LogErrorMessage('Es sind ' + IntToStr(SQLDS_Work.RecordCount) + ' Wareneingänge aktiv. Fehler bei Timer_Chart aufgetreten');
          end;
          Exit;
        end
        else if SQLDS_Work.Fields[0].AsInteger = 0 then begin // Fehler weil kein Wareneingang aktiv
          Exit;
        end
        else if SQLDS_Work.Fields[0].AsInteger = 1 then begin
          SQLDS_Work.Close;
          SQLDS_Work.CommandText := 'SELECT [IdentNr], [WareneingangsNr] from WEP02.dbo.WareneingangsNr WHERE Aktiv = 1';
          SQLDS_Work.Open;
          if SQLDS_Work.FieldCount > 1 then begin
            IdentNr := SQLDS_Work.Fields[0].AsString;
            WENr := SQLDS_Work.Fields[1].AsString;
          end;
        end;
      end;
      IdentNrOrig := IdentNr;
      IdentNr := StringReplace(IdentNr, '-', '', [rfReplaceAll]);
      SQLDS_Work.Close;
      SQLDS_Work.CommandText := 'Select COUNT(Gesamtergebnis) from WEP02.dbo.Teil_' + IdentNr + ' WHERE Gesamtergebnis = 1';
      SQLDS_Work.Open;
      if SQLDS_Work.FieldCount > 0 then begin
        Gut := SQLDS_Work.Fields[0].AsInteger;
      end;
      SQLDS_Work.Close;
      SQLDS_Work.CommandText := 'Select COUNT(Gesamtergebnis) from WEP02.dbo.Teil_' + IdentNr + ' WHERE Gesamtergebnis = 0';
      SQLDS_Work.Open;
      if SQLDS_Work.FieldCount > 0 then begin
        Schlecht := SQLDS_Work.Fields[0].AsInteger;
      end;

      La_Wenummer.Caption.Text := WENr;
      La_Teilenummer.Caption.Text := IdentNr;

      SQLDS_Work.Close;
      SQLDS_Work.CommandText := 'Select (Teilename) from WEP02.dbo.Teile WHERE IdentNr = ' + quotedStr(IdentNrOrig);
      SQLDS_Work.Open;
      if SQLDS_Work.FieldCount > 0 then begin
        Teilename := SQLDS_Work.Fields[0].AsString;
        Delete(Teilename, 1, 4);
        if not TryStrToInt(Teilename, SelectedModell) then begin
          SelectedModell := 0;
        end;
      end;
Offensichtlich knallt es in der dbxmss.dll.
Allerdings wundert mich das, da ich gelesen habe das es bei multithreading keine Probleme gibt,
wenn jeder Thread seine eigene Connection hat.
Die Exceptions treten meistens auf wenn auf Fields[] zugegriffen wird

Danke

nahpets 4. Dez 2015 11:33

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Mehrere Vorschläge:
Fehler abfangen:
Delphi-Quellcode:
  SQLDS_Work.Close;
  SQLDS_Work.CommandText := 'Select (Teilename) from WEP02.dbo.Teile WHERE IdentNr = ' + quotedStr(IdentNrOrig);
  Try
    SQLDS_Work.Open;
    if SQLDS_Work.FieldCount > 0 then begin
      Teilename := SQLDS_Work.Fields[0].AsString;
      Delete(Teilename, 1, 4);
      if not TryStrToInt(Teilename, SelectedModell) then begin
        SelectedModell := 0;
      end;
    end;
  except
    on e : Exception do begin
      if Assigned(Logger) then begin
        Logger.LogErrorMessage(SQLDS_Work.CommandText);
        Logger.LogErrorMessage(e.Message);
      end;
    end;
  end;
Analog bei allen SQLDS_Work.Open einbauen.
Delphi-Quellcode:
        SQLDS_Work.Open;
        if SQLDS_Work.FieldCount > 0 then begin
ist gut und schön, aber was ist, wenn die Ergebnismenge (warum auch immer) leer ist?
Delphi-Quellcode:
  try
    SQLDS_Work.Open;
    if SQLDS_Work.Active then begin
      if not SQLDS_Work.Eof then begin
        if SQLDS_Work.FieldCount > 0 then begin
          ...
        end;
      end else begin
        if Assigned(Logger) then begin
          Logger.LogErrorMessage(SQLDS_Work.CommandText);
          Logger.LogErrorMessage('sinnvoller Text zur Fehlerbeschreibung (leere Ergebnismenge)...');
        end;
      end;
    end else begin
      if Assigned(Logger) then begin
        Logger.LogErrorMessage(SQLDS_Work.CommandText);
        Logger.LogErrorMessage('sinnvoller Text zur Fehlerbeschreibung (geschlossene Datenmenge) ...');
      end;
    end;
  except
    on e : Exception do begin
      if Assigned(Logger) then begin
        Logger.LogErrorMessage(SQLDS_Work.CommandText);
        Logger.LogErrorMessage(e.Message);
      end;
    end;
  end;
Diese Fehlerbeschreibung hilft nicht wirklich
Zitat:

Die Exceptions treten meistens auf wenn auf Fields[] zugegriffen wird
Da ja an mehreren Stellen auf Fields[] zugegriffen wird, passiert der Fehler bei irgendeinem der Fields[] oder bei einem Bestimmten und wenn bei einem Bestimmten, bei welchem?

Und wenn sie nicht bei Fields[] auftreten, wo denn dann bitte sonst noch?

Ist die Ergebnismenge der Abfrage leer, dürfte ein Zugriff auf Fields[] kaum funktionieren, da es ja keinen Datensatz in der Ergebnismenge gibt und damit FieldCount = 0 ist. Was ich momentan aber nicht weiß und beurteilen kann ist, ob ein Zugriff auf FieldsCount bei einer leeren Ergebnismenge möglich ist oder hier bereits ein Fehler auftreten kann.

1. Abfrage öffnen.
2. prüfen, ob Abfrage geöffent werden konnte.
3. wenn nein, Meldung ausgeben und abbrechen.
4. prüfen, ob Ergebnismenge leer ist.
5. wenn ja, Meldung ausgeben und abbrechen.
6. prüfen, ob Anzahl der erwarteten Felder in der Ergebnismenge vorhanden ist.
7. wenn nein, Meldung ausgeben und abbrechen.
8. Werte auslesen und verarbeiten.

Um alles herum per Try Except unerwartete Fehler abfangen und im Fehlerfalle Meldung ausgeben und abbrechen.

Abbrechen heißt hier: Entweder das Programm beenden oder in einem sicheren und konsistenten Zustand weiterarbeiten.

quaero 4. Dez 2015 11:47

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Warum sollte FieldCount nichts zurückliefern? Selbst bei
Code:
SELECT * FROM relation WHERE 1=0
werden alle Felder der Relation zurückgegeben. Und
Code:
SELECT COUNT(irgendwas) FROM relation
gibt immer auch einen Wert zurück. Selbst wenn kein Datensatz die Bedingung erfüllt oder relation leer ist, wird 0 zurückgeliefert.

nahpets 4. Dez 2015 11:55

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Wenn dem so ist, können wir diese Fehlerursache ja ausschließen.

Klar, bei 'nem Count bekommt man mindestens 0 zurück, wenn es was nicht gibt.

Was ist bei
Delphi-Quellcode:
SQLDS_Work.CommandText := 'SELECT [IdentNr], [WareneingangsNr] from WEP02.dbo.WareneingangsNr WHERE Aktiv = 1';
?
Wenn's das nicht gibt, ist die Ergebnismenge leer, EoF müsste dann erfüllt sein und FieldCount = 2. Fields[0].AsString geht dann und gibt 'ne leere Zeichenfolge zurück.

Gut, können wir den Fehler auch ausschließen.

p80286 4. Dez 2015 12:32

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Ich frage mich was die beiden Code-Auszüge miteinander zu tun haben. Im ersten wird irgendetwas mit einer TCP-Verbindung gemacht, im zweiten werden Daten aus einer DB per SQL abgeholt. Wenn es da irgendwelche Seiteneffekte gibt, tippe ich auf Pointer-Arithmetik von Zauberlehrlingen.

Gruß
K-H

P.S.
was ist mit durchsteppen mittels F7/F8 kann da der Fehler eingegrenzt werden?

nahpets 4. Dez 2015 12:45

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
@p80286

naja, im "Thread" gibt es sowas wie DataNotInDB und DataExport.
Es könnte sich hier unter Umständen, eventuell, vielleicht um Funktionen handeln, die auf uminöse Weise auf die Datenbank zugreifen. Hier ließen sich dann Probleme und Konflikte beim Zugriff auf die Datenbank nicht zwingend ausschließen.

Im Eingangspost heißt es ja u. a.
Zitat:

... in einem Thread Daten via TCPIP von einem Server gelesen werden und in eine MSSQL Datenbank eingegraten werden...
Wie dies konkret funktioniert, ist aus dem zur Verfügung gestellten Quelltext aber leider nicht zu entnehmen.

Momentan gibt es noch zuviele :glaskugel:, aus denen gelesen werden muss, um sich dem gestellten Problem anzunähern.

p80286 4. Dez 2015 15:34

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Bei soviel Konjunktiv kann ich Dir nicht widersprechen. es kann natürlich sein, daß zwei Threads sich ins Gehege kommen, das sollte aber nur der Fall sein wenn sie sich eine Ressource teilen müssen, aber welche mag das wohl sein.....:glaskugel:

Gruß
K-H

Barthiboy 5. Dez 2015 15:58

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Hallo
danke für die vielen Antworten.
@p80286
also die zwei Codeschnipsel stellen die Datenbankzugriffe da, die in der Anwendung gemacht werden.

Programmübersicht:
Also in Einem Thread wird eine TCPIP Kommunikation aufgebaut. Hier werden Daten abgeholt und dann mittels SQLStatements
in die Datenbank geschrieben.

Im VCL-Thread wird in der Datenbank nachgeschaut (select count...) wie viele Datensätze denn schon eingetragen wurden und dies wird dann visualisiert.

In dem TCPIP Thread passiert eigentlich nicht recht viel mehr. Hier wir weiter oben lediglich Kommunikationsgeschichten abgearbeitet. Daher dann die Frage Datacount > 0 und dann gehts los mit Datenbank eintragen.
Die Eigenschaften des Threads wie z.B.: DataNotinDB oder weitere sind mittels CriticalSections geschützt und haben ihre Get und Set Methoden mit denen darauf zugegriffen wird

Ich habe selbstverständlich schon mit F7/F8 versucht zu debuggen. Allerdings lässt sich der Fehler nicht genau lokalisieren.
Also ich arbiete mit XE5 Enterprise und witzigerweise schmiert der Debugger auch ab wenn diese $C000005 Exception auftritt.
Ich kann am Montag gerne einen Auszug davon einstellen.


Zitat:

Im Eingangspost heißt es ja u. a.
Zitat:
... in einem Thread Daten via TCPIP von einem Server gelesen werden und in eine MSSQL Datenbank eingegraten werden...
Wie dies konkret funktioniert, ist aus dem zur Verfügung gestellten Quelltext aber leider nicht zu entnehmen.
Ja stimmt. Werde ich natürlich nachschicken.
Aber vorab schon mal so viel.
In der Funktion ProduktAuswerten wird das gemacht.
Hier werden die SQLStatments generiert und abgesetzt.

Jetzt noch eine ganz grundsätzliche Frage.
Es ist rein theoretisch schon möglich von zwei Threads aus auf die selbe Datenbank zuzugreifen?
Da hier ja der fehler in der dbxmss.dll auftritt.

Vielen Dank
einen schönen Abend

Barthiboy 7. Dez 2015 05:58

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Liste der Anhänge anzeigen (Anzahl: 1)
Hallo.
Im Anhang ist jetzt das .pas file in dem der Thread gestartet wird und die Kommunikation läuft.
Den anderen Datenbankzugriff im Main findet ihr ja auf der ersten Seite.
Ich hoffe das jetzt nicht mehr fehlt.
Ich werde jetzt trotzdem gleich mal den Vorschlag von nahpets umsetzen und sehen ob ich zu einem Ergebnis komme.
Wenn ihr Vorschläge hab, nur her damit

Barthiboy 11. Dez 2015 05:17

AW: TSQLDataSet, TSQLConnection Multithread Exception
 
Hallo,
nach längerem vergeblichem probieren bin ich jetzt auf FireDAC umgestiegen.
Einach alle Komponenten ausgetauscht.
Und es funktioniert


Alle Zeitangaben in WEZ +1. Es ist jetzt 01:17 Uhr.

Powered by vBulletin® Copyright ©2000 - 2024, Jelsoft Enterprises Ltd.
LinkBacks Enabled by vBSEO © 2011, Crawlability, Inc.
Delphi-PRAXiS (c) 2002 - 2023 by Daniel R. Wolf, 2024 by Thomas Breitkreuz