Wielowątkowy dostęp do bazy PostgreSQL

0

Mam pytanko. Napisałem program, który w oparciu o timer pobiera pewne informacje z bazy, a następnie wysyła je na e-mail. Wewnątrz procedury tworzę dynamicznie dwa query (zeos), które pobierają dane z bazy. Program działa bez zarzutu, jednak gdy uruchomię tą samą procedurę w klasie TThread to sypie accesami na prawo i lewo. Domyślam się, że gdzieś odnoszę się do wątku, który już nie istnieje, jednak nie umiem tego namierzyć. Moglibyście rzucić okiem?

procedure TfrmMain.timStartTimer(Sender: TObject);
var
  ind: integer;
  sqlZap: string;
  sList: TStringList;
begin
  qLST_ZAD.Close;
  qLST_ZAD.Open;
  qLST_ZAD.First;
  try
    sList := TStringList.Create;
    while not qLST_ZAD.Eof do
    begin
      try
        if not sList.Find(qLST_ZAD.FieldByName('idx_zadania').asstring, ind) then
        begin
          TStartEvent.Create(qLST_ZAD.FieldByName('idx_zadania').AsInteger);
//          WysWiad(qLST_ZAD.FieldByName('idx_zadania').AsInteger); //jak wywołuję samą procedurę bez wątku to jest ok
          sList.Add(qLST_ZAD.FieldByName('idx_zadania').AsString);
          sList.Sort;
        end;
        qLST_ZAD.Next;
      except
        sqlZap := 'mailing.log_dodaj(' + #39 + 'Nie udało się uruchomić zadania nr: '+
                  qLST_ZAD.FieldByName('idx_zadania').AsString + #39 + ', ' +
                  '1);';
        OpenSQL(sqlZap);
      end;
    end;
  finally
   sList.Free;
  end;
end;

i procedura z wątku:

 
constructor TStartEvent.Create(id: Integer);
begin
  inherited Create(False); // wywołanie wątku
  Fid := id; // przypisanie wartości do zmiennej
end;

procedure TStartEvent.Execute;
begin
  FreeOnTerminate := True; // zwolnij po zakończeniu wątku
  WysWiad(Fid);
end;

procedure WysWiad(id: integer);
var
  q, qZ: TZQuery;
  sqlZap, tmp, msg_tmp, gDef: string;
  lEmail: TEmail;
  Freq, TimeStart : Int64;
begin
  //główna procedura systemu
  try
    QueryPerformanceCounter(TimeStart); // myslalem poczatkowo ze to wina nazwy
    qZ := TZQuery.Create(frmMain);
    qZ.Name := 'qZ' + FloatToStr(TimeStart); //unikalna nazwa dla kazdego watka
    qZ.Connection := frmMain.db;
    qZ.SQL.Text := 'SELECT idx_znacznika, znacznik, pole FROM mailing.znaczniki';
    qZ.Open;
    gDef := frmMain.AfOpen(qZ); //tu jest petla while po calym query ktore zwraca stringa np w postaci np: "u.IMIE "Imie", u.NAZW "Nazwisko" itd ...
    q := TZQuery.Create(frmMain);
    q.Name := 'qTMP' + FloatToStr(TimeStart);
    q.Connection := frmMain.db;
    q.SQL.Text := SQL_Select + gDef + SQLForm;
    q.ParamByName('id').AsInteger := id; //id przekazany z timera
    try
      q.Open;
      if q.RecordCount = 0  then
        Exit;
    except
      sqlZap := 'mailing.log_dodaj(' +#39+ 'Błąd parametów w zapytaniu SQL: ' + q.SQL.Text + #39+ ', 1);';
      OpenSQL(sqlZap);
      Exit;
    end;
    q.First;
    while not q.Eof do
    begin
      qZ.First;
      msg_tmp := q.FieldByName('tresc').AsString;
      if qZ.RecordCount > 0 then
      begin
        while not qZ.Eof do
        begin
          tmp := Copy( qZ.FieldByName('pole').AsString,
                       Pos('"', qZ.FieldByName('pole').AsString)+1,
                       PosEx('"', qZ.FieldByName('pole').AsString, Pos('"', qZ.FieldByName('pole').AsString)+1) - Pos('"', qZ.FieldByName('pole').AsString)-1
                     );
          if tmp <> '' then
          begin
            msg_tmp := StringReplace(msg_tmp,
                                   qZ.FieldByName('znacznik').AsString,
                                   q.FieldByName(tmp).AsString,
                                   [rfReplaceAll]);
          end
          else
            msg_tmp := StringReplace(msg_tmp,
                                   qZ.FieldByName('znacznik').AsString,
                                   qZ.FieldByName('pole').AsString,
                                   [rfReplaceAll]);
          qZ.Next;
        end;
      end;
      if ((StrToTime(q.FieldByName('start_od').AsString) <= Time) and
          (StrToTime(q.FieldByName('start_do').AsString) >= Time)) then
      begin
        msg_tmp := StringReplace(msg_tmp, #39, '', [rfReplaceAll]);
        if msg_tmp <> '' then

          TEmail.Create(q.FieldByName('mail').AsString,
                                q.FieldByName('temat').AsString,
                                msg_tmp,
                                q.FieldByName('idx_maila').AsInteger); //tu inny TThread ale z nim raczej nie ma problemu bo sypie sie wczesniej
      end;
      q.Next;
    end;
    if ((q.RecordCount > 0) and (msg_tmp <> '')) then
    begin
      sqlZap := 'mailing.zadania_start(' + IntToStr(id) + ', ' +
                                         BoolToStr(TRUE,True) + ');';
      StrToBool(OpenSQL(sqlZap));  //zaktualizowaliśmy pole start więc timer nie będzie więcej wyszukiwał tego zadania
    end;
  finally
    q.Free;
    qZ.Free;
  end;
end;
0

VCL nie jest ThreadSafe (a zdecydowanie nie cały) i nie możesz sobie przerzucać referencji z jakiejś formy (czy nawet formy) do metody wątku.
A więc żadnych takich:

qZ := TZQuery.Create(frmMain);

tylkoqZ := TZQuery.Create(nil);

A tego typu konstrukcja:
```delphi
gDef := frmMain.AfOpen(qZ); //tu jest petla while po calym query ktore zwraca stringa np w postaci np: "u.IMIE "Imie", u.NAZW "Nazwisko" itd ...

Jest **absolutnie ** nie do przyjęcia.

Poza tym, każdy, powtarzam KAŻDY (wynika, to że nie każdy klient bazy danych jest bezpieczny wątkowo. A jeżeli jest, to może być tak, że komponenty z których korzystasz nie są. A ZEOS to nie jest kod najwyższych lotów...) wątek powinien mieć swoją własną prywatną instancję połączenia (Connection) do bazy danych.
A więc, nie tak, że przepisujesz referencję połączenia do Query z formy (głównej aplikacji?! sic!!):

qZ.Connection := frmMain.db;

Tylko, coś takiego:zConn := TXConnectio.Create();qZ.Connection := zConn;

Oczywiście, jak coś stworzysz to i nie zapominaj tego potem zniszczeć.

Powodzenia!
0

Zastosowałem się do twoich wskazówek, wszystko tworzę dynamicznie, zamiast frmMain jest nil, każdy wątek ma swoje query, dodatkowo przeniosłem całą procedurę jako procedurę wewnętrzną klasy TStartEvent oraz poprawiłem jeszcze kilka innych rzeczy tutaj nie opisanych ale wg. twoich wskazówek i chyba działa. Browar dla Ciebie

0

Musi działać!
A co browaru - Stary, jakbym za każdą pomoc dostawał jeden mały browarek, to byłbym non-stop napruty ;-)
Poza tym, na grupę dyskusyjną (http://groups.google.com/group/pl.comp.lang.delphi/topics i https://groups.google.com/group/pl.comp.lang.delphi.bazy-danych/topics?hl=pl) zapraszam - tam siedzi zdecydowanie więcej ludzi ze zdecydowanie większym doświadczeniem.
No offence - oczywiście :)

Tak MisiekD - ten sam, no chyba że znasz jakiegoś innego wloochacz'a.
Ja nie znam :)

1 użytkowników online, w tym zalogowanych: 0, gości: 1