[ salvaric @ 11.11.2015. 08:52 ] @
Pozdrav,

Nisam imao ranije iskustva sa Thread-ovima, pa me interesuje par stvari oko funkcionisanja.

Napravio sam u zasebnom fajlu Thread koji uzima podatke iz TSQLQuery-a i smešta ih u lokalnu TCombox, i nakon izvršenja uz proceduru Synchronize prenosi podatke u TComboBox na određenoj formi.

Code:
unit ThreadS;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, sqldb, db,StdCtrls,Forms,Controls;

  type
    {TGetComboItems}
    TGetComboItems = class(TThread)
        public
          IsFinish : Boolean;
          constructor Create(sSQL,sField,sFieldID: String;
                             ComboBox:TComboBox; sIndex: Integer);
          destructor Destroy; override;
        protected
          S: String;
          tQry: TSQLQuery;
          tSQL: String;
          tCombo,Combo: TComboBox;
          tField: String;
          tFieldID: String;
          tIndex: Integer;
          procedure Execute; override;
          procedure AddToCombo;
          procedure ShowStatus;
          procedure SetCursorWait;
          procedure SetCursorDefault;
    end;

implementation

uses modul1,glava1s;

{ TGetComboItem }

constructor TGetComboItems.Create(sSQL, sField, sFieldID: String;
                                  ComboBox: TComboBox; sIndex: Integer);
begin
     FreeOnTerminate:=true;
     tQry := TSQLQuery.Create(nil);
     tQry.DataBase    := modul.konekcija;
     tQry.Transaction := modul.Transakcije;
     tQry.SQL.Clear;
     tQry.SQL.Add(sSQL);
     tQry.Open;
     tField:=sField;
     tFieldID:=sFieldID;
     tIndex:=sIndex;
     tCombo:=ComboBox;
     tCombo.Clear;
     tCombo.Enabled:=false;
     Combo:=TComboBox.Create(nil);
     IsFinish:=false;
     inherited Create(false);
end;

destructor TGetComboItems.Destroy;
begin
  tQry.Free;
  Combo.Free;
  Synchronize(@SetCursorDefault);
  inherited Destroy;
end;

procedure TGetComboItems.Execute;
begin
   if not Terminated then
    begin
     Synchronize(@SetCursorWait);
     s:='Učitavanje podataka je u toku...';
     Synchronize(@ShowStatus);
     with tQry do
      Begin
        while not eof do
          begin
            if (not IsFinish) then
              begin
                Combo.Items.AddObject(FieldByName(tField).AsString
                                     ,TObject(FieldByName(tFieldID).AsInteger));
                Synchronize(@ShowStatus);
                Next;
              end
            else
               Last;
          end;
       Close;
      end;
     IsFinish:=true;
     s:='Učitavanje podataka je izvršeno.';
     Synchronize(@ShowStatus);
     Synchronize(@AddToCombo);
    end;
end;

procedure TGetComboItems.AddToCombo;
begin
     tCombo.Items:=Combo.Items;
     tCombo.ItemIndex:=tCombo.Items.IndexOfObject(TObject(tIndex));
     tCombo.Enabled:=true;
end;

procedure TGetComboItems.ShowStatus;
begin
    Form1.Status.SimpleText:=s;
end;

procedure TGetComboItems.SetCursorWait;
begin
  Screen.Cursor:=crSQLWait;
end;

procedure TGetComboItems.SetCursorDefault;
begin
  Screen.Cursor:=crDefault;
end;

end.



Thread pozivam na sledeći način:

Code:

procedure TForm1.firma_izmClick(Sender: TObject);
var
    sFIRMA: PFirma;
    GetMesta : TGetComboItems;
begin

          // Učitavanje gradova i tekućih računa u ComboBox i indeksiranje
          GetMesta:=TGetComboItems.Create('select * from tab_mesta','mesto','id', firma_mesto, sFIRMA^.ID_MESTA);
          GetMesta.Start;
          ....
end;    


1. Interesuje me, da li je potrebno i na koji način se vrši oslobađanje memorije od Thread-a, ili se on sam uništi nakon izvršenja, pošto sam pokušavao da pozovem FreeAndNil(GetMesta) nakon izvršenja al izbaci grešku.

Iz Thread-om punim Combo jer sam želeo da se prvo otvori forma i dozvole korekcije ostalih podataka dok se i combo ne učita, jer koristim MySql sa net servera pa se podaci malo sporije učitavaju, čisto da se ne gubi vreme na učitavanje.

2. Da li je potrebno osloboditi Objekte iz ComboBox-a posle korišćenja, il je dovoljno ComboBox.Clear, pošto je objekat tipa INTEGER, ne dozvoljava mi da izvršim Object.Free nad bilo kojim slogom.

3. I da li je OK način na koji sam to rešio.

[ savkic @ 11.11.2015. 10:47 ] @
> 1. Interesuje me, da li je potrebno i na koji način se vrši oslobađanje memorije od Thread-a, ili se on sam
> uništi nakon izvršenja, pošto sam pokušavao da pozovem FreeAndNil(GetMesta) nakon izvršenja al izbaci grešku.

Ako je setovan FreeOnTerminate onda i ne moras da unistavas thread ako nije onda moras (kao i svaki drugi objekat)

> Iz Thread-om punim Combo jer sam želeo da se prvo otvori forma i dozvole korekcije ostalih
> podataka dok se i combo ne učita, jer koristim MySql sa net servera pa se podaci malo sporije učitavaju,
> čisto da se ne gubi vreme na učitavanje.

Bilo koja vizuelna kontrola je vezana za glavni thread, nikako je ne smes koristiti direktno iz pomocnog threada.
Alociraj obican TStringList i u njega ubaci vrednosti koje zelis.

> 2. Da li je potrebno osloboditi Objekte iz ComboBox-a posle korišćenja, il je dovoljno ComboBox.Clear,
> pošto je objekat tipa INTEGER, ne dozvoljava mi da izvršim Object.Free nad bilo kojim slogom.

Sam si odgovorio, kako integer vrednosti nisu klase (objekti) već prosti tipovi nema potrebe da bilo šta radiš osim da isprazniš ili uništiš string listu koja ih je sadržala.

> 3. I da li je OK način na koji sam to rešio.

Moraš proveriti i da li su db komponente thread safe ili da li prave posebnu konekciju ka bazi, ako ne onda ni njih ne smeš koristiti u pomoćnom threadu na taj način. Generalno, na dobrom si putu ali imaš prostora da to bolje izvedeš i središ klasu.
[ salvaric @ 11.11.2015. 17:04 ] @
Kreirao sam u samom Thread-u Connection i Transaction komponente, podatke iz Query-a sam prebacio da idu u TStringList, i nakon završetka Synchronize procedurom ubacim u ComboBox, povezao sam i sve lepo radi.

Jedini problem kojo je sad nastao jeste usporeno zatvaranje programa, 3-4 sekunde nakon klika na X, samo u koliko se koristi taj thread, u koliko ne program se redovno zatvara, ne mogu da prokljuvim u čenu je caka.

Sve komponente koje se kreiraju unutar thread klase sam stavio da se oslobađaju, u proceduri Destroy.
[ salvaric @ 11.11.2015. 20:42 ] @
Code:

unit ThreadS;

{$mode objfpc}{$H+}

interface

uses
  Classes, SysUtils, sqldb,mysql55conn,StdCtrls,Forms,Controls;

  type
    {TGetComboItems}
    TGetComboItems = class(TThread)
        private
          S: String;
          tCombo: TComboBox;
          StrList: TStringList;
          tField: String;
          tFieldID: String;
          tSQL: String;
          tIndex: Integer;
          tQ : TSQLQuery;
          tConn : TMySQL55Connection;
          tTrans : TSQLTransaction;
          procedure SetEnable;
          procedure SetDisable;
          procedure PropertyQuery;
        public
          IsFinish : Boolean;
          constructor Create(sSQL,sField,sFieldID: String;
                             ComboBox: TComboBox; sIndex: Integer);
          destructor Destroy; override;
        protected
          procedure Execute; override;
          procedure AddToCombo;
          procedure ShowStatus;
          procedure SetCursorWait;
          procedure SetCursorDefault;
    end;

implementation

uses glava1s;

{ TGetComboItem }

procedure TGetComboItems.SetEnable;
begin
     tCombo.Enabled:=true;
end;

procedure TGetComboItems.SetDisable;
begin
     tCombo.Enabled:=false;
end;

procedure TGetComboItems.PropertyQuery;
begin
  // kreiranje konekcije
     tConn := TMySQL55Connection.Create(nil);
     tConn.Hostname := 'localhost';
     tConn.DatabaseName := 'baza';
     tConn.UserName := 'root';
     tConn.Password := 'root';
     tConn.CharSet:='latin1';
  // kreiranje transakcije
     tTrans := TSQLTransaction.Create(Nil);
     tTrans.Database := tConn;
  // kreiranje Query-a
     tQ := TSQLQuery.Create(nil);
     tQ.DataBase    := tConn;
     tQ.Transaction := tTrans;

     tConn.Transaction := tTrans;
  // Otvaranje konekcije i tabele
     tConn.Connected:=true;
     tQ.SQL.Add(tSQL);
end;


constructor TGetComboItems.Create(sSQL, sField, sFieldID: String;
                                  ComboBox: TComboBox; sIndex: Integer);
begin
     FreeOnTerminate:=true;
     StrList := TStringList.Create;
     tField  := sField;
     tFieldID:= sFieldID;
     tIndex  := sIndex;
     tCombo  := ComboBox;
     tSQL    := sSQL;
     IsFinish:= false;
     inherited Create(true);
end;

destructor TGetComboItems.Destroy;
begin
     FreeAndNil(StrList);
     tConn.Connected:=false;
     FreeAndNil(tConn);
     FreeAndNil(tQ);
     FreeAndNil(tTrans);
     inherited Destroy;
end;

procedure TGetComboItems.Execute;
begin
   if not Terminated then
    begin
     Synchronize(@SetDisable);
     Synchronize(@SetCursorWait);
     s:='Učitavanje podataka je u toku...';
     Synchronize(@ShowStatus);
     PropertyQuery;
     with tQ do
      Begin
        Open;
        while not eof do
          begin
            if (not IsFinish) then
              begin
                StrList.AddObject(FieldByName(tField).AsString
                                     ,TObject(FieldByName(tFieldID).AsInteger));
             //   Synchronize(@ShowStatus);
                Next;
              end
            else
               Last;
          end;
         close;
         tConn.Connected:=false;
      end;
     IsFinish:=true;
     s:='Učitavanje podataka je izvršeno.';
     Synchronize(@ShowStatus);
     Synchronize(@AddToCombo);
     Synchronize(@SetCursorDefault);
     Synchronize(@SetEnable);
    end;
end;

procedure TGetComboItems.AddToCombo;
begin
     tCombo.Clear;
     tCombo.Items.AddStrings(StrList);
     tCombo.ItemIndex:=tCombo.Items.IndexOfObject(TObject(tIndex));
     tCombo.Enabled:=true;
end;

procedure TGetComboItems.ShowStatus;
begin
     Form1.Status.SimpleText:=s;
end;

procedure TGetComboItems.SetCursorWait;
begin
     Screen.Cursor:=crSQLWait;
end;

procedure TGetComboItems.SetCursorDefault;
begin
     Screen.Cursor:=crDefault;
end;

end.
[ salvaric @ 11.11.2015. 20:50 ] @
Sve lepo radi, ne prijavljuje nikakvu grešku, sem što se forma zamrzme par sekundi prilikom gašenja.
[ captPicard @ 11.11.2015. 22:21 ] @
Pokreni 20ak puta za redom i prati zauzeće memorije pa ćeš vidjeti da li oslobađa ili ne.
[ savkic @ 11.11.2015. 22:24 ] @
> Jedini problem kojo je sad nastao jeste usporeno zatvaranje programa, 3-4 sekunde nakon klika na X, samo u koliko se koristi taj thread, u koliko ne
> program se redovno zatvara, ne mogu da prokljuvim u čenu je caka.

Da li thread radi u tom trenutku (uzima podatke iz baze)? Ako da onda u unutrasnju petlju za db stavi i not Terminated uslov a iz glavnog threada pozovi Terminate u OnClose eventu.
[ salvaric @ 11.11.2015. 22:47 ] @
Memorija se oslobađa, pratio sam. Pokrenuo sam alatku Thread (View->Debug Windows->Threads), u njoj se prikažu svi thread-ovi koji su aktivni u aplikaciji, prikaže se i ovaj u trenutku kad se izvršava i kad se izvrši nestane, što znači da je završen. Proveravao sam u Destroy proceduri, posle FreeAndNil(tConn) sa Assigned(tConn) i tako svaku komponentu koja se oslobađa i nijedna ne postoji nakon oslobađanja.
[ Rapaic Rajko @ 12.11.2015. 08:16 ] @
Ako sam dobro shvatio, ti osim konekcije u thread-u imas i konekciju u aplikaciji (u mainthread-u)?

Malo sam proguglao, vezano za taj scenario. Ako sam dobro shvatio pricu, to sto kod tebe radi bez problema je cist sticaj okolnosti.
Mozda nema veze s tvojim slucajem, ali ipak pogledaj: http://forum.lazarus.freepascal.org/index.php?topic=27379.0 .
Javi jel sta pomoglo.

Pozz
[ salvaric @ 12.11.2015. 10:29 ] @
Hvala Rajko,

dodao sam na glavnu formu SQLDBLibraryLoader1 komponentu i aktivirao je nakon startovanja programa i rešila je problem, nema pauze pri gašenju.

Net je čudo, svaka čast!
[ salvaric @ 12.11.2015. 11:10 ] @
Dal neko ima ideju kako da iz Thread-a da pozovem proceduru sa parametrima u glavnom thread-u?

Pošto Savkić kaže da izbegavam pozivanje i podešavanje vizuelnih komponenti iz Thread-a, nisam siguran da se to može izbeći u mom slučaju. Ovaj Thread sam zamislio da bude univerzalan za sve forme u projektu, a pozivam ga iz glvnog thread-a, pre pokretanja bilo koje forme, čak i u nekim delovima glavne forme se primenjuje. Dodatni problem jeste što se u određenoj proceduri poziva više puta na izvršenje u isom momentu, tipa na formi koja ima više ComboBox-ova koje treba napuniti i staviti Enable na false dok se ne učita.

Pokušao sam u glavnom thread-u da deklarišem promenjive i proceduru u "public", koju bi mogao pozavti iz mog Thread-a i menjati sa Synchronize(@Form1.SetComboBox), ali će doći do sukoba promenjive u koliko se budu izvršavala 2-3 thread-a u isto vreme.

Ili možda ideju kako da prenesem TStringList iz thread-a u glavni thread i da se učita u combo nakon završetka thread-a?

Nadam se da sam napisao šta sam mislio a da je to i razumljivo i drugima.



[Ovu poruku je menjao salvaric dana 12.11.2015. u 12:21 GMT+1]
[ savkic @ 12.11.2015. 22:18 ] @
> Dal neko ima ideju kako da iz Thread-a da pozovem proceduru sa parametrima u glavnom thread-u?

Možeš preko Synchronize, ima i drugih (boljih) načiuna ali je ovo najlakši.

> Pošto Savkić kaže da izbegavam pozivanje i podešavanje vizuelnih komponenti iz Thread-a, nisam siguran da se to može izbeći u mom slučaju.

Synchronize se izvršava u kontekstu glavnog threada tako da si bezbedan.

> Ovaj Thread sam zamislio da bude univerzalan za sve forme u projektu, a pozivam ga iz glvnog thread-a, pre pokretanja bilo koje forme, čak i
> u nekim delovima glavne forme se primenjuje. Dodatni problem jeste što se u određenoj proceduri poziva više puta na izvršenje u isom momentu, tipa na
> formi koja ima više ComboBox-ova koje treba napuniti i staviti Enable na false dok se ne učita.

Važna stvar, nemoj thread posmatrati kao upravljački blok (to je uvek main thread) već kao običan worker deo, svrha threada je da izvrši neki zadatak u pozadini, kada završi može poslati neku notifikaciju pozivaocu (glavnom threadu), PostMessage je zgodan mehanizam ali takodje i TEvent. Glavni thear u međuvremenu radi svoj posao i kada stigne obaveštenje o završetku posla, obradi to što je završeno.

> Pokušao sam u glavnom thread-u da deklarišem promenjive i proceduru u "public", koju bi mogao pozavti iz mog
> Thread-a i menjati sa Synchronize(@Form1.SetComboBox), ali će doći do sukoba promenjive u koliko se budu
> izvršavala 2-3 thread-a u isto vreme.

U datom trenutklu samo jedan thread (glavni) može izvršavati Synchronize metodu, svi ostali čekaju na red.

> Ili možda ideju kako da prenesem TStringList iz thread-a u glavni thread i da se učita u combo nakon završetka thread-a?

Pošto je sve to u istom procesu, prosto pošalješ referencu na temprorary TStrings preko PostMessage, glavni thread uradi šta treba i oslobodi memoriju a pomoćni thread pravi novu kad mu treba.


[ salvaric @ 11.12.2015. 21:29 ] @
Naišao sam na problem sa više Synchronize procedura u Execute thread-a,

thread se nekoliko puta izvrši kako treba (nekad 10 nekad 20 puta), malo sam ga forsirao da proverim da ne dođe do problema, kad eto, bolje da nisam, šalim se.

U execute pozivam nekoliko Synchronize procedura koje izvršavaju različite operacije, npr.
1. otvaranje slash forme
2. upis u statusbar teksta nakon svake promene
3. kreiranje i otvaranje Query tabele i konekcije
4. zatvaranje slash forme
...

Negde sam na netu pronašao da je preporučeno samo jedno pozivanje Synchronize procedure unutar execute thread-a.

Pretpostavljam da dođe do zakucavanja thread-ova zbog tih multi procedura koje se sihhronizuju u isto vreme.

Nisam mogao naći neki wait te Synchronize procedure, kako bi sačekao da se izvrši pa tek onda da se nastave naredne.

Čak sam razmišljao da napravim zasebne thread-ove koji će te operacije obavljati, i u execute ih pozovem i sačekam izvršenje, mislim da je to i jedino rešenje.
[ savkic @ 12.12.2015. 09:23 ] @
Synchronize se izvrsava u kontekstu glavnog threada, nemoguće je da se dve Synchronize metode izvršavaju istovremeno.

U svakom slučaju to što ti radiš ne treba raditi, glavna forma tj. glavni thred je upravljač programa, delegira i zadaje radne naloge a izvršioci (thredovi) ih rade i obaveštavaju o završetku zadatka (uspešno, neka greška i slično), glavna forma dalje odlučuje šta će raditi sa tim obaveštenjem (ignorisati, prikazati nešto u statusbaru, otvoriti/zatvoriti splash formu i slično). Dakle, nemoj ništa raditi sa UI iz threada, uradi šta se traži i obavesti naručioca o tome.
Moj ti je savet da zaboraviš na Synchronize i nikad ga ne koristiš i imaćeš mnogo brži program i manje problema u radu (mada je kod tebe nešto drugo problem).
[ salvaric @ 12.12.2015. 13:20 ] @
U pravu si Savkiću, nije u tome problem.

Jedna procedura je punila TVirtualStringTree tabelu iz Query-a, i u njoj je problem pretpostavljam.

Code:

type
  { TCusField }

  TCusField = record
     Index    : Integer;
     Value    : variant;
     FieldName: String;
  end;  
...
type
  PData = ^TData;
  TData = object  
     Field      : array of TCusField;
     RecNo      : Integer;
     inicialize : Boolean;
     public
       function ValueOfFiledName(sName: String): variant;
       function IndexOfName(sName: String): integer;
       procedure SetValue(sName: String; Value: variant);
       procedure SetIndexValue(i: Integer; Value: variant);

  end; 
...
procedure TSetTable.SetLocalTable;
var
  Data      : PData;
  Node      : PVirtualNode;
  CustField : TCusField;
  i         : Integer;
begin
      with TVirtualStringTree(fSender) do
         begin
            BeginUpdate;
            Clear;

            RootNodeCount := tQ.RecordCount;
            NodeDataSize  := SizeOf(TCusField);   // <  mislim da je ovde negde problem
            Node          := GetFirst;

            while not tQ.EOF do
              begin
                Data := GetNodeData(Node);
                SetLength(Data^.Field,tQ.FieldCount);
                Data^.RecNo := tQ.RecNo;
                for i := 0 to High(Data^.Field) do
                  begin
                     CustField.Value     := tQ.Fields[i].Value;
                     CustField.FieldName := tQ.Fields[i].FieldName;
                     CustField.Index     := i;
                     Data^.Field[i]      := CustField;
                  end;
                Data^.inicialize    := true;
               Node:= GetNext(Node);
               end;
            end;
            EndUpdate;
         end;
end; 


Ne mogu da nahvatam gde ga zakuca, ne pokazuje nikakvu grešku, samo se zamrzne. U thread-ovima vidim da su dva pokrenuta i čekaju u trenutku zamrzavanja i ništa više, dok u momentima kad prolazi pokrene se jedan i uništi nakon izvršenja.

Milslim da je nastaje problem oko tog NodeDataSize, al mi nije jasno kako nekad hoće 20 puta a nekad se zakuca na 5.
[ salvaric @ 12.12.2015. 17:18 ] @
Verovali ili ne u ovoj liniji je problem :
Code:
with TVirtualStringTree(fSender) do
zašto, kako ne znam. fSender je TObject od TVirtualStringTree koji prosleđujem prilikom kreiranja thread-a.
[ ((BugA)) @ 12.12.2015. 17:37 ] @
Je l` ovaj poslednji kod ima veze sa onim sto si postavljao u prethodnim porukama? Da se ne udubljujem sad u sve, ako nema potrebe. Ako ne, onda je vrlo nezahvalno bilo sta komentarisati iz delova koda, pogotovo kada je rec o thread-ovima. Ako si ceo kod vec postavio, onda da gledam ispocetka :)

Drugo, vidim da koristis pokazivace - jesu li podaci kojima pristupas "thread-safe", tj. ne moze da se desi da vise thread-ova odjednom drlja po njima (pise/brise)?

A to sto jednom hoce 20 puta, a jednom zakuca posle 5-og, to je lepota mutli-threading programiranja ;) Salu na stranu, koncept je vrlo prost, kad ga jednom sazvaces, ali se eventualne greske obicno tesko otkrivaju, upravo sto nemas potpunu kontrolu nad time koji se deo koda kad izvrsava, i kada ce se koji thread-ovi "nesrecno" preklopiti. Uglavnom, najbolje testiranje je kad najvise opteretis svoju logiku (sto vise thread-ova), tada se greske najlakse pojave.

E sad, kako ces da ih debug-ujes, to zavisi... Recimo, madExcept je dobar (besplatan za nekomercijalnu upotrebu), ima mogucnost da baci exception kad se aplikacija zakuca, a i daje iscrpan (i prilicno vrlo precizan) call stack, pa lako vidis koji thread gde stoji, pomazuci da odgonetnes i zasto stoji.
[ salvaric @ 12.12.2015. 17:49 ] @
Ima veze, u pitanju je isti program i problem.
[ salvaric @ 12.12.2015. 17:56 ] @
Malo glupoo pitanje, al šta mogu, pošto nemam nikakva iskusta sa thread-ovima, i priliično sam samouk, kako da proverim dal je TVirtualStringTree "thread-safe" komponenta?
[ ((BugA)) @ 12.12.2015. 20:25 ] @
Nazalost, "thread-safe" je takodje koncept koji se mora postovati, nije osobina koja se tek tako moze proveriti, i obicno je najveca prepreka/problem kod rada sa thread-ovima. I nije glupo pitanje.

Sustina "thread-safe" podataka je da ne dozvolis da nekoj promenljivoj moze pristupati vise thread-ova odjednom - ako ce svi samo da citaju iz nje (read), to i nije problem, ali ako ce makar jedan da je menja (write) na bilo koji nacin, e onda:

(1) U trenutku dok je jedan thread menja nijedan drugi ne sme da cita iz nje - mada i tu postoji izuzetak, kada se radi o tipovima koje procesor cita/pise u jednom "cugu", pa onda je to samo po sebi "thread-safe", najbolji primer je obicno Boolean tip, neke vrste integer-a, itd. U suprotnom, moze doci do neocekivanih rezultata, npr. da thread procita polovicno promenjenu vrednost.
(2) Ako je jedan thread menjao promenljivu, drugi mora da bude spreman na to - npr. kad jedan pozove Free() na objektu, drugi vise ne sme da pristupa tom objektu (ili vec mora da bude spreman da on ne postoji, pa da dalje radi sta treba u tom slucaju).

Zapravo, "thread-safe" i nije osobina podatka (kopomonente, cega god), vec nacina pristupa/rada sa istim. Da bi odredjeni podatak (ili struktura, objekat) bio "thread-safe", programer mora da ispostuje odredjene principe/pravila prilikom pristupa i rada sa tim podacima, najcesce kroz odredjene sisteme sinhronizacije - critical section, semaphore, mutex, itd.

I kada imas (do tada) "thread-safe" pristup podacima, dovoljno je da negde uradis nesto nepromisljeno, pa da to pocne da pravi problem. "Thread-safe" je nesto sto se mora postovati sve vreme, uvek.

Oblast je podugacka da bih ti je ovde prepricavao (a i nisam trenutno bas dobar sa vremenom), najbolje ce biti da sam malo istrazis na tu temu, pa da eventualno pitas ako negde zapnes. U medjuvremenu, videcu da pogledam kod koji si postavio do sad, pa da ti eventualno ukazem na neke propuste, ukoliko ih ima, a uocim ih.

U svakom slucaju, multi-threading je jedan od tezih koncepata u programiranju, najvise zbog stalne potrebe za sinhronizacijom i poteskocama kod testiranja, da se otkriveni bug ponovi/debug-uje.
[ salvaric @ 12.12.2015. 21:42 ] @
Hvala Igore na obrazloženjima problematike,

ipak se vraćam na upravljanje komponenti iz mainthread-a, eventualno neke manje stvari da obrađujem iz drugih thread-ova.

Prebacio sam proceduru u mainthread, Application.ProcessMessages joj daje malo bolji efekat unutar procedure, i radi kako treba.

Testirao sam for petljom proceduru 500 puta, fercera kako treba.

Hvala još jednom svima!
[ ((BugA)) @ 13.12.2015. 00:00 ] @
Nema na cemu, mislim da ti to i jeste najbolji pristup, sto ti je i savkic vec pisao.

Bukvalno, uprosceno, imas dugme "Obradi" na ciji pritisak pocinje neka duza obrada podataka koja ti "zakljuca" main (UI) formu, pa ti aplikacija deluje blokirano ("zamrznuto"). Promenis logiku da se pritiskom na dugme samo dugme disable-uje (kako ne bi moglo ponovo da se klikne), i da se pokrene poseban thread koji ce da odradi obradu - za to vreme tvoja aplikacija normalno odgovara na komande korisnika, nije zamrznuta. Po zavrsetku obrade, thread signalizira main (UI) formi (thread-u) da je obrada gotova (PostMessage, TEvent, sta god), i ti prikazes podatke (i po potrebi ponovo enable-ujes dugme).

Jedino na sta treba ovde obratiti paznju jeste da neka druga komanda ne moze da poremeti reakciju na zavrsetak thread-a - recimo ako neko u medjuvremenu izotvara neke druge forme (dok tvoj thread "obrada" jos uvek radi, jer forma vise nije zakljucana/blokirana, pa moze da se po njoj klikce), kad jednom thread zavrsi moras misliti na to sta ce se dalje desiti - ako saljes poruku koja ce za rezultat imati upisivanje neke vrednosti u npr. edit komponentu main forme, pritom je i fokusirajuci, to moze biti problem ako preko toga imas jos neke (u medjuvremenu) otvorene forme, pa tvoja edit komponenta ne moze da prihvati fokus (jer je i cela forma u pozadini, neaktivna), cak mozes dobiti i exception (npr. "cannot focus disabled window", ili tako nesto), sto sigurno nije prijatno za krajnjeg korisnika, niti pohvalno za tvoju aplikaciju.

Thread-ovi mogu biti jako zgodni, ali su u pocetku mozda malo tezi za shvatanje jer razbijaju uobicajeni "linearni" tok izvrsavanja programa, gde u svakom trenutku znas sta ce se, i kada desiti. Kod thread-ova to ne vazi, i jedino je bitno da kad se nesto desi (kad kog to bilo), ti budes spreman da na to adekvatno reagujes, bez ugrozavanja/remecenja stabilnosti programa, i njegovog trenutnog stanja - koje se verovatno promenilo od trenutka pokretanja thread-a, jer to i jeste sustina, da vise stvari moze da se izvrsava paralelno, ali je programer taj koji mora viditi racuna da to izvrsavanje bude uskladjeno, da paralelna desavanja ne uticu negativno jedno na drugo.

p.s. Eh, sad sam tek primetio da si ubacio Application.ProcessMessages(), to je vec nesto sto treba zdusno izbegavati... :/ Da, u vecini slucajeva ce mozda raditi posao, ali je malo aljkav pristup programiranju. Ne kazem, nekad treba odraditi posao u roku, uzeti novac i preziveti mesec, ali kad mogucnosti dopustaju, izbegavati.

Zasto? Zato sto isto razbija linearni/ocekivani tok programa, ali kada se to (naj)manje ocekuje. Npr, imas gorepomenuto dugme "Obrada" na koje se pokrece neko procesuiranje, i uglavnom je tad cela aplikacija "zamrznuta" dok se procesuiranje ne zavrsi - sto znaci da inace nema nikakve potrebe da disable-ujes dugme (iako je to mozda dobra praksa, ali je u ovom slucaju nepotrebno).

Medjutim, kad u toku procesuiranja pozivas Application.ProcessMessages(), ti dozvoljavas formi da u medjuvremenu odgovara na Windows poruke (pa ona vise nije "zamrznuta"), ali to ima i jedan sporedni efekat - tada ce korisnik moci ponovo da klikne na dugme "Obrada" (koje prethodno nije imalo potrebe da bude disable-ovano), pa ce se procedura za obradu pokrenuti ponovo (dok se prethodna jos nije zavrsila), sto tek moze imati neocekivane efekte.

Ovo je samo jedan banalan primer (evo ovde upravo pricaju o tome -- http://delphi.about.com/od/obj...-processmessages-dark-side.htm), mozes ih naci jos, svejedno vredi istraziti malo i na tu stranu. Ponavljam, generalno bi to trebalo izbegavati, ali kako je realan zivot obicno (bar) malo van teorije, jedino ti znas sta ti u datoj situaciju radi posao, i da li cilj opravdava sredstvo. Jedino bi dobro bilo da budes svestan da to nije najsretnije resenje.
[ savkic @ 13.12.2015. 00:27 ] @
Citat:
salvaric:
Malo glupoo pitanje, al šta mogu, pošto nemam nikakva iskusta sa thread-ovima, i priliično sam samouk, kako da proverim dal je TVirtualStringTree "thread-safe" komponenta?


Ni jedna VCL komponenta nije thread safe jer sam VCL nije thread safe. Da bi došao do thread safe window handla morao bi da ga sam kreiraš (zaobidješ VCL kompletno). Dakle, sve što ima veze sa UI (user interfejsom) nije thead safe.
[ Rapaic Rajko @ 14.12.2015. 08:33 ] @
Da li se pomenuta metoda TSetTable.SetLocalTable poziva kroz Synchronize?
Ako DA, pomenuti kod bi morao da radi. Jedino bih dodao Application.ProcessMessages u petlju, i nakon izlaska iz nje. To osigurava da MainThread odradi sve pratece akcije/poruke koje idu uz to pumpanje tabele, PRE nego sto neki drugi thread kroz svoj Synchronize mehanizam pristupi istoj komponenti.

Nisam siguran kako sad radi Synchronize, ali nekad je bilo ovako: thread posalje poruku WM_SYNCHRONIZE glavnom MainThread-u, i ovaj je kroz message handler odradi. Jedno polje poruke WM_SYNCHRONIZE sadrzi pointer na threadmethod koji treba izvrsiti, i to je to. Sad je jasnije zasto treba staviti Application.ProcessMessages gore u kod.

Pozz

P.S. Ako bi i dalje pucalo, nista lakse: postoji TMonitor klasa i metode Enter i Exit.

P.P.S. I da, with() konstrukciju sto manje koristiti; ima neobjasnjivih bug-ova s ovim, video svojim ocima (nije D sto je nekad bio).
[ salvaric @ 14.12.2015. 09:09 ] @
Da, poziva se kroz Synchronize.

Bekapovao sam projekat pre promena, tako da ću proveriti dal može tako, nisam u mythread-u stavljao Application.ProcessMessages u metodu.

Usput, koristim Lazarus, i u pravu si sa tom with() konstrukcijom i kod Lazarusa, i kad sam prebacio tu proceduru u main thread morao sam da to uklonim jer je isto pravila zakucavanja, ne znam zašta, al je tako i čim sam išao direktno sve jo bilo ok.
[ savkic @ 14.12.2015. 10:53 ] @
Proveri da li postoje svezije verzije DB library koji koristis za rad sa bazom, ranije su one bile nestabilne i pravile probleme kod FPCa.
[ salvaric @ 14.12.2015. 21:55 ] @
Rešen problem,

školski primer ne razmatranja celokupne situacije. MySql baza koju sam koristio je na web serveru je limitirana (5 konekcija), a zanemario sam da već imam 13 client aplikacija koje na svakih 30 sekundi osvežavaju bazu, prebacio sam je na localhost i funkcioniše savršeno, tako da je verovatno u određenom momentu bilo 5 konekcija nakon čega dođe do bug-a.

Hvala svima!
[ salvaric @ 15.12.2015. 11:43 ] @
Procedura u main thread-u:
Code:
       for i:=0 to 20 do
                 begin
                  MyThread  := MyThread.Create(history.TAB_HISTORY,'',PConnect);
                  MyThread.Start;
                  MyThread.WaitFor;
                  history.ShowModal;
                 end; 

history je forma u kojoj prikazujem promene nad slogom date tabele u main formi, i tabela u njoj se puni iz MyThread-a.

i ovde nasteje neki sukob thread-ova, i dolazi do zakucavanja.

U koliko ne koristim WaitFor, prikaže mi se history forma kao da sam stavio "history.Show", ali ne kao ShowModal, i u koliko kliknem na main form forma ode u pozadinu a aktivira se main forma.

A kad koristim history.Show, sve radi kako treba i 1000 puta, al mi forma history nije na vrhu, tj. jeste dok ne kliknem na main formu.

Ovu proceduru sam napravio radi testa, al bi je koristio samo bez petlje.

[ salvaric @ 15.12.2015. 21:07 ] @
Rešeno, zatvorite temu kako ne bi više mogao pisati, pozdrav!
[ Rapaic Rajko @ 16.12.2015. 07:16 ] @
Citat:
salvaric:
Procedura u main thread-u:
Code:
       for i:=0 to 20 do
                 begin
                  MyThread  := MyThread.Create(history.TAB_HISTORY,'',PConnect);
                  MyThread.Start;
                  MyThread.WaitFor;
                  history.ShowModal;
                 end; 

history je forma u kojoj prikazujem promene nad slogom date tabele u main formi, i tabela u njoj se puni iz MyThread-a.

i ovde nasteje neki sukob thread-ova, i dolazi do zakucavanja.

U koliko ne koristim WaitFor, prikaže mi se history forma kao da sam stavio "history.Show", ali ne kao ShowModal, i u koliko kliknem na main form forma ode u pozadinu a aktivira se main forma.

A kad koristim history.Show, sve radi kako treba i 1000 puta, al mi forma history nije na vrhu, tj. jeste dok ne kliknem na main formu.

Ovu proceduru sam napravio radi testa, al bi je koristio samo bez petlje.



Hm, pa malo je cudan test: kreiras 20 thread-ova koji svi rade nad jednom istom formom..?
A da ti forma 'ostane na vrhu' pogledaj property StayOnTop.

Pozz
[ salvaric @ 16.12.2015. 08:06 ] @
Kod mene je to malo haotičnije, pa zbog toga ne izbacujem čitav kod.

Problem se javio iz razloga što u datom thread-u pre izvršenja poziva se splash forma (loading...) i na kraju zatvrara, bez tog radi kako je i napisano gore u kodu, što sam naknadno proverio i konsatovao. Korigovao sam malo thread, prebacio sam na ručno uništenje, FreeOnTerminate sam stavio false i u proceduri posle WaitFor thread-a dodao MyThread.Free i to je rešilo problem.