|
[ franjo_tahi @ 17.02.2009. 00:02 ] @
| Do sada nisam koristio thread-ove i čini mi se komplicirana priča. Pokušao sam po netu pronači primjere, ali nema ono što mi treba, a ono što ima, kada sam preradio - nije radilo (:
Da li ima tko volje da napiše jednostavan programčić koji ima:
2 x qry
2 x dbgrid
1 x gumb
pritiskom na gumb bi se trebali otvoriti qry1 i qry2, svaki u svom thread-u i rikazati svaki svoje podatke u dbgridovima.
Kako i kada brisati thread?
|
[ obucina @ 17.02.2009. 02:11 ] @
Pa, praksa na ovom forumu uglavnom je takva da se ne piše kod po zahtevu, ali ako pošalješ to što si do sada uradio, sigurno ćeš dobiti savete šta tu nije dobro.
Nit (thread) se završava kada se završi izvršavanje Execute procedure. Ako nit treba da se izvršava do isključivanja programa onda moraš s vremena na vreme proveravati Property Terminated, da vidiš da li je zahtevan prekid rada. Odmah iza toga ga možeš izbrisati u bilo kom trenutku. Možeš ga brisati sa Free ili mu postaviti svojstvo FreeOnTerminate na true pa će se sam izbrisati kada završi posao. Sam ćeš ga oslobađati onda kada su ti nakon završetka izvršavanja niti potrebne vrednosti njegovih svojstava. Npr sa:
while not Terminated do
begin
end;
Query komponente otvori u Execute metodi, normalno, sa Open. U thread dodaj jos jednu proceduru, bez parametara, u kojoj ćeš Query povezati sa dbgridom, tj TDataSource koji je povezan na grid ćeš povezati sa Query komponentom.
procedure TThread.ConnectToGrid();
begin
grid.datasource.dataset := query;
end;
a u Execute stavljas
Query.open;
Synchronize(ConnectToGrid);
Synchronize služi da se neka metoda pozove u sinhronizaciji sa glavnom niti aplikacije, onom koja obrađuje događaje korisničkog interfejsa. Na taj način neće ti se dešavati "sudari" između delova programa koji se ne mogu izvršavati zajedno (npr zato što pristupaju istom resursu). Postoje i druge metode za sinhronizovanje više niti, preko kritičnih sekcija ili Windows događaja.
[ savkic @ 17.02.2009. 09:00 ] @
> Do sada nisam koristio thread-ove i čini mi se komplicirana priča. Pokušao sam po netu pronači primjere, ali nema ono što mi treba, a ono što
> ima, kada sam preradio - nije radilo (:
Za početak kreni od jednostavnog Threads primera u Demos diru Delphija. Takođe imaš i nekoliko DB primera, potraži sve gde se spominje TThread.
> Da li ima tko volje da napiše jednostavan programčić koji ima:
> 2 x qry
> 2 x dbgrid
> 1 x gumb
> pritiskom na gumb bi se trebali otvoriti qry1 i qry2, svaki u svom thread-u i rikazati svaki svoje podatke u dbgridovima.
Ne možeš imati Delphi kontrole (forme) koje će se izvršavati u posebnom threadu. Drži se pravila da sve vizuelne stvari moraju biti u glavnom threadu i da nemaš resurse kojima dva threada mogu istovremeno pristupati.
Ako su ti gridovi u glavnom threadu moguće je imati qveri komponente u posebnom, bitno je da su baza i komponente koje koristiš threadsafe, ako nisu moraš imati i posebne DB connection, transaction komponente za svaki kveri.
[ franjo_tahi @ 17.02.2009. 10:57 ] @
Znam da nije praksa pisati kod, treba mi primjer ili dobar manual.
U zasebni thread bih stavio query (i transakciju ako treba), tako da mi se svaki qry otvara u svom thread-u. U programu na formi ponekad otvaram 10-tak qry-a pri prikazivnju forme, vjerojatno bi se na taj način ubrzao program.
Da li je uopče moguće pirkazati podatke u gridu iz qry-a koji nije u istom thread-u ili otvoriti qry koji je prosljeđen thread-u kao parametar?
[ viking13 @ 17.02.2009. 12:32 ] @
Pogledaj ThreadedQueries demo iz Unified Interbase (UIB).
http://www.progdigy.com/?page_id=5
[ Rapaic Rajko @ 17.02.2009. 13:14 ] @
1) Moguce je da svaki query izvrsis u zasebnom thread-u. Takodje i iz thread-a da povezes podatke sa grid-om; kad se query izvrsi, preko Synchronize metode obavestis grid da su podaci raspolozivi (recimo, sa MyQuery.EnableControls).
Treba obratiti paznju da li je baza threadsafe, ako nije, moraces u svoju klasu/omotac oko thread-a (ili u samo klasu thread-a) da ugradis threadsafe; pogledaj TRTLCriticalSection u helpu.
2) Ubrzati program (sa vise thread-ova) neces, jedino sto ces dobiti je smooth prikaz (nemam bolji izraz) - prividno istovremeni prikaz svih podataka. Medjutim, ako je radilo sporo sa jednim (main) thread-om, radice sporo (pa i sporije) sa vise thread-ova. Jedino ako imas double/quad core procesor, tacno toliko ce biti ubrzanje prikaza u odnosu na jedan thread - nista vise.
3) Zasto obican worker thread nije moguce povezati sa vizuelnom kontrolom? Zato sto Windows (kao "event oriented OS") radi preko poruka. Sve akcije, pa i iscrtavanje kontrola se izvrsavaju, kontrolisu i odjavljuju porukama. Bilo ko (nas thread) ko zeli da se ukljuci u pricu mora imati mehanizam za rad sa porukama - "window handle" i "message queue". Nas worker thread ih nema, i to je prosto TO. Ali zato nas thread moze da poturi main thread-u (koji ima sve sto treba) da uradi vizuelni deo za njega.
4) Ima jedna stvar sa Synchronize, a to je da po pozivu iste worker thread CEKA da se izvrsi, odnosno da mainthread "vrati loptu". To moze biti ogranicenje kad se rade neke ozbiljnije sinhronizacije. Alternativni nacin je da se posalje poruka sa PostMessage(), ali uz malo vise kodiranja (za iole slozenije akcije): potrebno je osmisliti tipove poruka i napisati handler-e za iste.
Kako to izgleda kad vise worker thread-ova salje zahteve (Synchronize) glavnom thread-u? Lepo, pozivi se izvrsavaju serijalizovano (za mainthread), i prividno stvari se desavaju istovremeno. Bas kao u (veoma lepom) demo primeru za thread-ove (sort aplikacija).
Ako nesto treba, tu smo... :)
Rajko
[ franjo_tahi @ 19.02.2009. 11:21 ] @
Iskušavao sam razne stvari. Zamisao mi je napraviti thread koji će se pokrenutu na početku programa i svakih x sekundi otvoriti i zatvoriti qry.
Ovo mi treba zbog postavki server koji skine konekciju ako nema prometa nekoliko minuta... Postoji bolje rješenje, ali, stranka ima svoje administratore i tako to :(
Napravio sam test prog. koji to radi, ali, kako da zaustavim thread nakon što ga jednom pokrenem i kako da napravim free?
Isto tako me zanima zašto unutar procedure Destroy ne mogu postaviti BreakePoint? Odnoso, kad ga psotavim i pokrenem prog, delphi ih prekriži (poput BreakPoint-a na komenaru)
napravio sam proceduru "StopRuning;" koja zaustavi timer, a time i izvršenje, ali ga ne mogu maknuti.
Kod je podjeljen u dva pas-a. Prvi je forma s dva guma: "Kreiraj" i "Uništi"
Može li netko ovo ispraviti da radi kako treba?
Form1.pas
Code:
var
Form1: TForm1;
tr: tNewThread;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
tr := tNewThread.Create(ibd, 1);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
tr.StopRuning;
tr.Terminate;
end;
UnitThread.pas
Code:
unit UnitThread;
interface
uses
Classes, SysUtils, ComCtrls, Windows, DB, IBDatabase, IBCustomDataSet, IBQuery, ExtCtrls;
type
tNewThread = class(TTHread)
private
qr: TIBQuery;
tr: TIBTransaction;
da: TIBDatabase;
ti: TTimer;
InExec: boolean;
procedure TimerExec(Sender: TObject);
protected
procedure Execute; override;
public
constructor Create(DataBase: TIBDatabase; IntervalSec: integer=10); reintroduce;
destructor Destroy;
procedure Terminated(Sender: TObject);
procedure StopRuning;
end;
implementation
{ tNewThread }
constructor tNewThread.Create(DataBase: TIBDatabase; IntervalSec: integer);
var i: integer;
begin
inherited Create(false);
self.FreeOnTerminate := true;
InExec := false;
OnTerminate := Terminated;
// kreiranje baze za konekciju
da := TIBDatabase.Create(nil);
da.DatabaseName := DataBase.DatabaseName;
da.Name := 'DatabaseTH';
da.LoginPrompt := false;
da.SQLDialect := 3;
for i := 0 to DataBase.Params.Count-1 do
da.Params.Append(DataBase.Params.Strings[i]);
// kreiranje transakcije
tr := TIBTransaction.Create(nil);
tr.DefaultDatabase := da;
tr.AllowAutoStart := true;
tr.DefaultAction := TACommit;
tr.Name := 'TeansactionTH';
tr.Params.Append('read_committed');
tr.Params.Append('rec_version');
tr.Params.Append('nowait');
da.DefaultTransaction := tr;
// kreiranje qr-y
qr := TIBQuery.Create(nil);
qr.Database := da;
qr.Transaction := tr;
qr.Name := 'QryTH';
qr.SQL.Append('select * from termini_kategorija');
ti := TTimer.Create(nil);
ti.OnTimer := TimerExec;
ti.Interval := IntervalSec * 1000;
ti.Name := 'TimerTH';
ti.Enabled := true;
end;
destructor tNewThread.Destroy;
begin
ti.Enabled := false;
// ako je pokrenuta procedure, čekaj da završi...
while InExec do Sleep(1000);
ti.Free;
if qr.Active then qr.Close;
if tr.Active then tr.Rollback;
if da.Connected then da.Close;
tr.Free;
qr.Free;
da.Free;
inherited;
end;
procedure tNewThread.Execute;
begin
inherited;
ti.Enabled := false;
InExec := true;
if not da.Connected then da.Open;
if not tr.Active then tr.StartTransaction;
qr.Open;
qr.Close;
InExec := false;
ti.Enabled := true;
end;
procedure tNewThread.StopRuning;
begin
ti.Enabled := false;
end;
procedure tNewThread.Terminated(Sender: TObject);
begin
ti.Enabled := false;
end;
procedure tNewThread.TimerExec(Sender: TObject);
begin
if not InExec then self.Execute;
end;
end.
[ savkic @ 19.02.2009. 13:02 ] @
Prvo izbaci TTimer iz thrada, drugo koristi Execute kao glavni upravljacki mehanizam.
Code:
procedure TMyThread.Execute;
var
Temp: Cardinal;
begin
while not Terminated do
begin
if GetTickCount - Temp > 10000 then
begin
qry.Open;
qry.Close;
Temp := GetTickCount;
end;
Sleep(100);
end;
end;
[ franjo_tahi @ 19.02.2009. 15:04 ] @
Hvala na pomoči. Napravio sam ono što mi je trebali i čini mi se da dobro radi.
Izmjenio sam proceduru i izbacio timer:
Code:
procedure tNewThread.Execute;
var
t1: TDateTime;
t2: Cardinal;
begin
while not Terminated do
begin
t1 := now;
if not da.Connected then da.Open;
if not qr.Transaction.Active then qr.Transaction.StartTransaction;
try
qr.Open;
qr.Close;
except
end;
// SQL qr-a je zadan kao vanjski parametar u Create proceduri
if qr.Transaction.Active then qr.Transaction.Rollback;
t2 := MilliSecondsBetween(now, t1);
// fInterval je određen u Create proceduri - zadan kao vanjski parametar.
if (fInterval - t2) > 0 then Sleep(fInterval - t2);
end;
end;
iz data modula ga pozivam iz procedure Database.AfterConnect
Code:
if CuvaVezu = nil then begin
CuvaVezu := tNewThread.Create(ibRadna, 'select * from termini_kategorija', 60)
end else begin
try
CuvaVezu.Resume;
except
end;
end;
e sad...
Kako uništiti thread u main formi?
Da li CuvaVezu.Terminate uništi thread ili samo zaustavi izvršavanje? Tj. ako napravim CuvaVezu.Terminate nakon toga, ako želim ponovo pokrenuti proces moram pozvati: Create?
Uočio sam da ako napravim CuvaVezu.Suspend, nakon toga CuvaVezu.Resume ponovo pokrene porces. Da li je to u redu ili treba drugačije?
[ savkic @ 19.02.2009. 17:50 ] @
> Kako uništiti thread u main formi?
Postavi (FreeOnTerminate i pozovi Thread.Terminate) ili (Thread.Terminate pa Thread.Free).
> Da li CuvaVezu.Terminate uništi thread ili samo zaustavi izvršavanje?
Samo postavi Terminated flag na true, što je dovoljno da se Execute metoda završi.
> Tj. ako napravim CuvaVezu.Terminate nakon toga, ako želim ponovo pokrenuti proces moram pozvati: Create?
Ako si uništio objekat onda ga ponovo moraš kreirati da bi mogao raditi sa njim.
> Uočio sam da ako napravim CuvaVezu.Suspend, nakon toga CuvaVezu.Resume ponovo pokrene porces. Da li je to u redu ili treba drugačije?
Suspend suspenduje thread, što znači da mu Windows više neće davati timeslice (dati mu mogućnost da se izvršava), Resume nastavlja izvršavanje threada od mesta na kome je stao. Ako ti samo želiš da na osnovu nekog uslova privremeno prekineš izvršavanje threada, možeš koristiti Suspend i Resume.
Inače Execute koji si napisao nije idelan, npr, ako je postavljeno da se kveri okida na 90 sekundi onda će proći 90 sekundi od poziva Terminate dok thread ne završi svoj rad, zato je bolji sistem koji sam ti napisao u prošloj poruci.
[ Rapaic Rajko @ 23.02.2009. 12:52 ] @
1) Izostavljen je override iz deklaracije Destroy; znaci
procedure Destroy; override;
Ovo mora, jer je procedura virtuelna; zato je compiler izbacivao breakpoint iz tvog koda. Kako si sad postavio, nista od unistavanja tvojih DB komponenata; program je samo izvrsavao osnovni TThread.Destroy destruktor.
2) Timer (bar u Delphiju) takodje radi preko poruka (sistem salje poruku); zato ovo i nije moglo da radi u thread-u. Jedini nacin da timer drugacije radi je direktno iz API-ja sa CreateTimer(), ali to je petljavina. Daleko je lepse raditi sve iz TThread.Execute metode.
3) Za dugacki interval (Sleep), a da bude bezbedan (umesto inertan), napravi proceduru MyTimer kojoj prosledis parametar koliki je zeljen iinterval; a u samoj proceduri ispucavas u petlji kratke intervale (recimo Sleep(50)). Uslov za petlju je da li je zeljeno vreme isteklo (imas GetTickCount funkciju), i pozeljno je da (u uslovu petlje takodje) proveravas neki spoljni flag, recimo upravo Terminated - sto bi znacilo urgentno se prekida sve, izlazis iz procedure MyTimer bez pardona itd.itd.
4) Poziv procedure TThread.Terminate ne odradjuje nista drugo, nego postavlja flag/property TThread.Terminated na true. A procedura Execute stalno chekira taj property, i tako to inace (treba da) radi.
Rajko
[ franjo_tahi @ 24.02.2009. 11:02 ] @
Napravio sam izmjene, onako kako sam shvatio, ako ima još primjedbi, molim....
poziv iz programa:
MyThread = tNewThread.Create(ibBaza, 'SELECT FIRST 1 ID FROM NEKA_TABLICA', 60);
ibBaza - baza na koju ide konekcija
60 - vrijeme u sec. za izvršenje select-a
Code:
unit UnitThread;
interface
uses
Classes, SysUtils, Windows, IBDatabase, IBQuery;
type
tNewThread = class(TTHread)
private
qr: TIBQuery;
tr: TIBTransaction;
da: TIBDatabase;
fInterval: integer;
protected
procedure Execute; override;
public
constructor Create(DataBase: TIBDatabase; SQLText: string; IntervalSec: integer); reintroduce;
destructor Destroy; override;
end;
implementation
{ tNewThread }
constructor tNewThread.Create(DataBase: TIBDatabase; SQLText: string; IntervalSec: integer);
var i: integer;
begin
inherited Create(false);
fInterval := IntervalSec * 1000;
self.FreeOnTerminate := true;
// kreiranje baze za konekciju
da := TIBDatabase.Create(nil);
da.DatabaseName := DataBase.DatabaseName;
da.Name := 'DatabaseTH';
da.LoginPrompt := false;
da.SQLDialect := 3;
for i := 0 to DataBase.Params.Count-1 do
da.Params.Append(DataBase.Params.Strings[i]);
// kreiranje transakcije
tr := TIBTransaction.Create(nil);
tr.DefaultDatabase := da;
tr.AllowAutoStart := true;
tr.DefaultAction := TACommit;
tr.Name := 'TeansactionTH';
tr.Params.Append('read_committed');
tr.Params.Append('rec_version');
tr.Params.Append('nowait');
da.DefaultTransaction := tr;
// kreiranje qr-y
qr := TIBQuery.Create(nil);
qr.Database := da;
qr.Transaction := tr;
qr.Name := 'QryTH';
qr.SQL.Append(SQLText);
end;
destructor tNewThread.Destroy;
begin
if tr.Active then tr.Rollback;
if da.Connected then da.Close;
tr.Free;
qr.Free;
da.Free;
inherited;
end;
procedure tNewThread.Execute;
var
Temp: Cardinal;
begin
while not Terminated do
begin
if not da.Connected then da.Open;
if not qr.Transaction.Active then qr.Transaction.StartTransaction;
try
qr.Open;
qr.Close;
except
end;
if qr.Transaction.Active then qr.Transaction.Rollback;
Temp := GetTickCount;
while (not Terminated) and ((GetTickCount - Temp) < fInterval) do begin
Sleep(100);
end;
end;
end;
end.
[ savkic @ 24.02.2009. 12:23 ] @
Nije ti dobra petlja, upit ce se izvrsavati na svakih 100ms.
> Temp := GetTickCount;
> while (not Terminated) and ((GetTickCount - Temp) < fInterval) do begin
Ovo je isto kao da si napisao:
while (not Terminated) and ((GetTickCount - GetTickCount) < fInterval) do begin
Osim toga ti treba da je proteklo vreme veće od zadatog intervala, ne manje.
[ franjo_tahi @ 24.02.2009. 14:21 ] @
Savkic, mislim da nisi gledao cijeli kod...
Code:
Temp := GetTickCount;
while (not Terminated) and ((GetTickCount - Temp) < fInterval) do begin
Sleep(100);
end;
1. prvi ulaz u petlju - zaista je isto onom što si napisao
2. Slip(100) - napravio pauzu od 100 ms
3. sljedeči ulaz u petlju:
Temp = stari broj ms,
GetTickCount = novi broj ms
novi broj - stari broj = razlika,
a razlika mi i treba...
Sleap se mora vrtiti zadato vrijeme, bolje nešto krače nego duže jer se veza sa serverom prekine nakon xx vremena. Na ovaj način je nešto duže jer za razliku koja je manja od 100 ms, napravit će pauzu od 100 ms.
[ savkic @ 24.02.2009. 14:52 ] @
> Savkic, mislim da nisi gledao cijeli kod...
U pravu si, kod je korektan, zbunila me je dodatna while petlja, komplikovana je za moj ukus :)
Samo jedan dodatak, kako imaš SELECT kveri ne treba ti Rollback već Commit.
[ Rapaic Rajko @ 24.02.2009. 21:03 ] @
Uh, a zasto ne ovako
Code:
procedure SmartSleep(aInterval: cardinal);
var
aTick: cardinal;
begin
aTick := GetTickCount + aInterval;
while not(Terminated) and (GetTickCount < aTick) do
Sleep(100);
end;
Rajko
[ franjo_tahi @ 25.02.2009. 07:53 ] @
Savkic, može pojašnjenje? Zašto Commit a ne Rollback? Cilj je zatvortiti transakciju. U čemu je kod ovog razlika?
Rapajicu, imaš pravo, kod tebe je samo jedna operacija zbrajanja, a kod mene nakon svakog komadića pauze. Tvoje oduzima manje procesorskog vremena. Promjenit ću kod sebe.
Palo mi je na pamet još nešto: da li bi isti efekt bio (održavanje veze sa serverom) kad bi se recimo, umjesto selecta, napravio ping ili nešto slično?
Thread bi trebao oduzimati što manje resursa. Ima li koji pametniji način za održavanje veze od selecta?
[ savkic @ 25.02.2009. 10:37 ] @
> Savkic, može pojašnjenje? Zašto Commit a ne Rollback? Cilj je zatvortiti transakciju. U čemu je kod ovog razlika?
Commit je efikasnija i veoma brza operacija, dok rollback može imati negativnih efekata na performanse, dobra je praksa koristiti commit kada god je moguće. U ovom konkretnom slučaju rollback će interno biti urađen kao commit (jer transakcija nije ništa izmenila).
Evo zanimljivog štiva na temu transakciju u IB/FB: http://www.ibphoenix.com/main....ibphoenix&page=ibp_expert4.
> Palo mi je na pamet još nešto: da li bi isti efekt bio (održavanje veze sa serverom) kad bi se recimo, umjesto selecta, napravio ping ili nešto slično?
> Thread bi trebao oduzimati što manje resursa. Ima li koji pametniji način za održavanje veze od selecta?
Ako je u redu da nemaš stalnu konekciju sa ciljnim računarom već da samo povremeno se povežeš i nešto uradiš onda je ping idealan.
[ franjo_tahi @ 25.02.2009. 11:24 ] @
Još pitanjece: Kako napraviti ping iz delphi-a?
[ savkic @ 26.02.2009. 00:09 ] @
Verovatno je nalakše iskoristiti postojeći ping utility iz windowsa koji bi startovao pomoću ShellExecute. I druga varijanta je posebna komponeta za to, npr.
TIdIcmpClient iz Indy biblioteke.
[ franjo_tahi @ 26.02.2009. 10:23 ] @
Napravio sam ping sa TIdIcmpClient-om, ali postoji problem: radi samo ako korisnik ima administratorska prava (na Visti, ne znam za druge).
ShellExecute radi, ali ne znam kako da dohvatim rezultat. U dosu je to radilo na način npr: "ping > test.txt" i rezultat je bio u test.txt file-u. Pokušao sam to učiniti sa ShellExecute, ali ne radi, tj. ne kreira test.txt.
postoji li način da se rezultat dobije u varijablu ili bar u file?
[ savkic @ 26.02.2009. 12:05 ] @
> Napravio sam ping sa TIdIcmpClient-om, ali postoji problem: radi samo ako korisnik ima administratorska prava (na Visti, ne znam za druge).
Neobično, mora da je ping pod nekim posebnim režimom.
> ShellExecute radi, ali ne znam kako da dohvatim rezultat. U dosu je to radilo na način npr: "ping > test.txt" i rezultat je bio u test.txt file-u.
> Pokušao sam to učiniti sa ShellExecute, ali ne radi, tj. ne kreira test.txt.
Možeš koristiti funkcije iz JCLa, CreateDOSProcessRedirected i WinExec32AndRedirectOutput.
[ Boris B. @ 27.02.2009. 16:24 ] @
>Neobično, mora da je ping pod nekim posebnim režimom.
Jeste pod posebnim rezimon, Indy ping koristi tzv. raw socket-e za cije koriscenje na NT-olikim windowsima su potrebna administratorska prava, zato sto raw socketi omogucavaju dostup do TCP packet header-a i njegovo spoofovanje, sto posledicno moze da izazove DoS i sl. Drugim recima Microsoft nas "stiti" od nas samih :)
Resenje je koriscenje skoro nedokumentovanog icmp.dll-a, koji je jos uvek prisutan na windows-ima, iako hoce da ga izbace iz API-ja vec godinama.
MSDN
http://support.microsoft.com/kb/170591
Delphi kod koji koristi ICMP.dll
http://delphi.about.com/od/internetintranet/l/aa081503a.htm
Copyright (C) 2001-2025 by www.elitesecurity.org. All rights reserved.
|