PDA

Visualizza la versione completa : [delphi] thread


123delphi321
10-03-2013, 12:57
Ciao a tutti,

ho fatto un programma che esegue il download di alcune immagini jpg da internet (fota da una webcam).

tale operazione dura un po di tempo, ed io vorrei inserire un pulsante per stoppare l'esecuzione di tale download.

credo (mi sembra di aver capito) che per implementare questa funzionalità devo uilizzare i delphi thread,...ma non riesco a capire bene il concetto di come fare questa cosa che sembrerebbe semplice...

mi potete indicare qualche esempio...

grazie

questo è il codice che eseguo per effettuare il download..


procedure TForm1.BtnScaricaFotoClick(Sender: TObject);
var
SourceFile, LocalFile: string;
ora:TdateTime;
SR : TSearchRec;
begin
if (varisnull(cxDateEditDal.editvalue)) or (varisnull(cxDateEditAl.editvalue)) then Abort;

try
screen.Cursor:=crHourGlass;
try
CreateDir(ExtractFilePath(Application.ExeName)+'im g')
except
end;

ora:=cxDateEditDal.Date;
while ora<=cxDateEditAl.Date do
begin
SourceFile := 'http://www.villamia.it/webcam/img_'+FormatDateTime('yyyymmdd',ora)+'_'+FormatDat eTime('hhnnss',ora)+'_0.jpeg';
LocalFile := ExtractFilePath(Application.ExeName)+'img\'+Extrac tUrlFileName(SourceFile);
LblFoto.Caption:='Download File...'+SourceFile;
LblFoto.Update;

if not FileExists(LocalFile) then
if URLDownloadToFile(nil, PChar(SourceFile), PChar(LocalFile), 0, nil) = 0 then
begin
try
cxImageFoto.Picture.LoadFromFile(LocalFile);
cxImageFoto.Update;
except
end;
end;
ora:=IncSecond(ora,1);
end;

screen.Cursor:=crDefault;
// ShowMessage('Download eseguito con successo!');
except
screen.Cursor:=crDefault;
// ShowMessage('Download non eseguito!')
end
end;

denis76
10-03-2013, 20:58
Comincia con creare un thread, avviarlo e fermarlo. Dopodiché cerca di gestire più di un thread ed alla fine mettici la funzione che scarica l'immagine. Non è difficile.

franzauker2.0
11-03-2013, 18:21
Sono due richieste diverse.
Se la domanda è "come fermo il download di qualcosa da internet con un bottone stop" è un conto.

Se la domanda è "come creo un thread che fa qualcosa, e lo interrompo quando voglio" è argomento diverso.

Con il corollario "voglio\posso usare componenti di terze parti già pronti"

franzauker2.0
11-03-2013, 18:25
Nel caso "1" ci vuole qualcosa tipo questo

function GetInetFile (const fileURL, FileName: String;i_dimensione:integer=0): boolean;
const
BufferSize = 1024;
var
hSession, hURL: HInternet;
Buffer: array[1..BufferSize] of Byte;
BufferLen: DWORD;
f: File;
sAppName: string;
scaricati:integer;

begin
frmlog.memlog.lines.clear;
frmlog.memlog.lines.add('Scaricamento da internet (si può premere STOP)');
if i_dimensione>0 then
frmlog.memlog.lines.add( 'Byte da trasferire '+(numeroconpuntini(i_dimensione)));
frmlog.show;
salvabtnstop;
frmlog.btnstop.enabled:=true;
application.processmessages;

if i_dimensione>0 then
frmlog.pgrprogressototale.max:=i_dimensione;

deletefile(pchar(filename));
result := false;
scaricati:=0;
sAppName := ExtractFileName(Application.ExeName) ;
hSession := InternetOpen(PChar(sAppName), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0) ;
try
hURL := InternetOpenURL(hSession, PChar(fileURL), nil, 0, 0, 0) ;
try
AssignFile(f, FileName) ;
Rewrite(f,1) ;
repeat
if not frmlog.btnstop.enabled then break;
function GetInetFile (const fileURL, FileName: String;i_dimensione:integer=0): boolean;
const
BufferSize = 1024;
var
hSession, hURL: HInternet;
Buffer: array[1..BufferSize] of Byte;
BufferLen: DWORD;
f: File;
sAppName: string;
scaricati:integer;

begin
frmlog.memlog.lines.clear;
frmlog.memlog.lines.add('Scaricamento da internet (si può premere STOP)');
if i_dimensione>0 then
frmlog.memlog.lines.add( 'Byte da trasferire '+(numeroconpuntini(i_dimensione)));
frmlog.show;
salvabtnstop;
frmlog.btnstop.enabled:=true;
application.processmessages;



if i_dimensione>0 then
frmlog.pgrprogressototale.max:=i_dimensione;

deletefile(pchar(filename));
result := false;
scaricati:=0;
sAppName := ExtractFileName(Application.ExeName) ;
hSession := InternetOpen(PChar(sAppName), INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0) ;
try
hURL := InternetOpenURL(hSession, PChar(fileURL), nil, 0, 0, 0) ;
try
AssignFile(f, FileName) ;
Rewrite(f,1) ;
repeat

//// ECCOLO QUI
if not frmlog.btnstop.enabled then break;
/////////////////
InternetReadFile(hURL, @Buffer, SizeOf(Buffer), BufferLen) ;
BlockWrite(f, Buffer, BufferLen);
if i_dimensione>0 then
begin
frmlog.memlog.lines[2]:= 'Scaricamento '+(numeroconpuntini(scaricati))+' bytes';
frmlog.pgrprogressototale.position:=scaricati;
end
else
frmlog.memlog.lines[1]:= 'Scaricamento '+trim(numeroconpuntini(scaricati))+' bytes';

application.processmessages;
scaricati:=scaricati+bufferlen;
application.processmessages;
until BufferLen = 0;
CloseFile(f) ;
result := True;
finally
InternetCloseHandle(hURL)
end
finally
InternetCloseHandle(hSession)
end;
if not frmlog.btnstop.enabled then
begin
deletefile(pchar(filename));
end;

frmlog.btnstop.enabled:=true;
ripristinabtnstop;
frmlog.hide;
end;


InternetReadFile(hURL, @Buffer, SizeOf(Buffer), BufferLen) ;
BlockWrite(f, Buffer, BufferLen);
if i_dimensione>0 then
begin
frmlog.memlog.lines[2]:= 'Scaricamento '+(numeroconpuntini(scaricati))+' bytes';
frmlog.pgrprogressototale.position:=scaricati;
end
else
frmlog.memlog.lines[1]:= 'Scaricamento '+trim(numeroconpuntini(scaricati))+' bytes';

application.processmessages;
scaricati:=scaricati+bufferlen;
application.processmessages;
until BufferLen = 0;
CloseFile(f) ;
result := True;
finally
InternetCloseHandle(hURL)
end
finally
InternetCloseHandle(hSession)
end;
if not frmlog.btnstop.enabled then
begin
deletefile(pchar(filename));
end;

frmlog.btnstop.enabled:=true;
ripristinabtnstop;
frmlog.hide;
end;


E' un po' brutale, ma penso si capisca

123delphi321
12-03-2013, 09:06
Originariamente inviato da franzauker2.0
Sono due richieste diverse.
Se la domanda è "come fermo il download di qualcosa da internet con un bottone stop" è un conto.

Se la domanda è "come creo un thread che fa qualcosa, e lo interrompo quando voglio" è argomento diverso.

ciao,
io pensavo fosse la stessa cosa..., e comunque io ho bisogno di premere un pulsante che mi ferma un processo. sia se questo è il download di un file da internet e sia se è il ciclo di elaborazione di una determinata operazione.
in linea generale vorrei capire, una volta per tutte, come funziona questo tipo di funzionamento del thread per poterlo inserire nelle mie procedure dove l'esecuzione di alcuni passi possa essere interrotto dall'operatore.

sto cercando di capire l'esempio che (con tanti ringraziamenti) hai postato...non mi sono chiare alcune cose:

fai riferimento ad un form frmlog in cui c'e' un btnstop ed un memo
poi fai riferimento a delle procedure salvabtnstop ripristinabtnstop che non so cosa facciano..

grazie

franzauker2.0
12-03-2013, 17:40
Originariamente inviato da 123delphi321
ciao,
io pensavo fosse la stessa cosa..., e comunque io ho bisogno di premere un pulsante che mi ferma un processo. sia se questo è il download di un file da internet e sia se è il ciclo di elaborazione di una determinata operazione.
in linea generale vorrei capire, una volta per tutte, come funziona questo tipo di funzionamento del thread per poterlo inserire nelle mie procedure dove l'esecuzione di alcuni passi possa essere interrotto dall'operatore.

sto cercando di capire l'esempio che (con tanti ringraziamenti) hai postato...non mi sono chiare alcune cose:

fai riferimento ad un form frmlog in cui c'e' un btnstop ed un memo
poi fai riferimento a delle procedure salvabtnstop ripristinabtnstop che non so cosa facciano..

grazie E' una banale form di log di testo, con dentro il bottone da premere.

Confermo che sono due "cose" estremamente diverse, così come la richiesta, e manca il riferimento all'utilizzo di componenti.

Andiamo per ordine: supponiamo di voler fermare un ciclo, qualsiasi
(un while ad esempio). Un metodo (non "il", ma uno) è quello di utilizzare un componente visuale, ad esempio un bottone, che viene posto in stato enabled=true all'inizio del ciclo.
Dentro il ciclo verifichi se il bottone è enabled: se NON lo è => esci
L'evento del bottone (onclick) sarà, ovviamente, t[speed]button(sender).enabled:=FALSE;

"Ogni tanto" poi ti servirà, nel ciclo, un application.processmessages (in realtà si può ottenere lo stesso effetto in modi diversi, ma certamente tanti esperti ti spiegheranno quali).
Questo forzerà la rilettura della coda dei messaggi windows che sono "parcheggiati" nella form (in sostanza la pressione del bottone).
All'uscita dal ciclo testi se il bottone è enabled: se non lo è => è stato premuto stop e quindi prendi in carico con qualcosa tipo "esecuzione terminata cazzi tuoi".

Puoi aumentare la velocità\aumentare la reattività del bottone STOP a seconda di quanto spesso fai fare il processmessages: più frequenti => più lento.
Un modo "normale" è quello di farlo ogni tot chunk di ciclo

chunk:=numerocicli div 100;

...
if (iterazione mod chunk)=0 then
begin
application.processmessages;
if NOT bottonestop.enabled then break;
end;

Altri approcci sono con un TTIMER, il quale a sua volta crea un vero e proprio thread.

===
Breve introduzione ai thread. Si tratta di pezzi di programma che vengono lanciati parallelamente al thread principale.
Si creano abbastanza facilmente, li si lancia, li si può fermare ("uccidendoli"), abbastanza facilmente anche con la libreria standard.

Quello che non è esattamente banale è sincronizzare i thread tra di loro e, in particolare, l'accesso a risorse condivise, che sono tipicamente i componenti VCL (visuali) e le eventuali strutture dati condivise (chessò un array globale).

Anche qui la faccenda è diverse se vuoi semplicemente fare un thread, e bloccarlo, oppure vuoi sfruttare più thread contemporanei (ad es. più core/CPU).

Nel primo caso la situazione è più semplice, in quanto tipicamente la sequenza del thread principale è
... crea thread
... lancia thread
... FAI QUALCOSA (qui c'è il problema, vedi poi)
... sincronizzati col thread terminato.

Il "fai qualcosa" è il problema, nel senso che puoi mettere a dormire il thread principale, ma devi agire a livello molto basso (non-VCL per capirci), oppure in questo ti serve un ciclo scemo che fa una sorta di busy waiting con l'oppurtuna istruzione per rilasciare rapidamente la CPU in idle.

A livello maggiore di complessità c'è la sincronizzazione vera e propria di thread diversi che, per quanto mi riguarda, uso solo nel caso in cui davvero serva un'elaborazione parallela (ad esempio calcolo di hash su file diversi, quanto piuttosto elaborazione di liste di dati tra di loro indipendenti).

Per questi lavori (ossia "veri") tipicamente utilizzo una libreria (ce ne sono tante) che rendono più agevole operare con tali oggetti.

ESSE-EFFE
12-03-2013, 18:04
Originariamente inviato da franzauker2.0
Altri approcci sono con un TTIMER, il quale a sua volta crea un vero e proprio thread.


Non so se sia cambiato qualcosa recentemente, ma credo che TTimer si basi sul messaggio WM_TIMER (viene creata una finestra nascosta), non su un thread parallelo.

123delphi321
12-03-2013, 19:34
ho utilizzato il primo metoto, che per il momento, sembra risolvere il mio problema...




procedure TForm1.BtnScaricaFotoClick(Sender: TObject);
var
SourceFile, LocalFile: string;
ora:TdateTime;
SR : TSearchRec;
begin
...
...
BtnStop.Enabled:=True;
BtnScaricaFoto.Enabled:=False;
ora:=cxDateEditDal.Date;
while ora<=cxDateEditAl.Date do
begin
application.processmessages;
if not BtnStop.Enabled then break;
SourceFile := 'http://www.villamia.it/webcam/img_'+FormatDateTime('yyyymmdd',ora)+'_'+FormatDat eTime('hhnnss',ora)+'_0.jpeg';
LocalFile := ExtractFilePath(Application.ExeName)+'img\'+Extrac tUrlFileName(SourceFile);
LblFoto.Caption:='Download File...'+SourceFile;
LblFoto.Update;

if not FileExists(LocalFile) then
if URLDownloadToFile(nil, PChar(SourceFile), PChar(LocalFile), 0, nil) = 0 then
begin
try
cxImageFoto.Picture.LoadFromFile(LocalFile);
cxImageFoto.Update;
except
end;
end;
ora:=IncSecond(ora,1);
end;
....
....

procedure TForm1.BtnStopClick(Sender: TObject);
begin
BtnStop.Enabled:=False;
BtnScaricaFoto.Enabled:=True;
end;


grazie

franzauker2.0
12-03-2013, 21:50
Originariamente inviato da ESSE-EFFE
Non so se sia cambiato qualcosa recentemente, ma credo che TTimer si basi sul messaggio WM_TIMER (viene creata una finestra nascosta), non su un thread parallelo. non puoi usare thread separato il timer serve per il processmessages del thread principale

123delphi321
13-03-2013, 08:56
ciao,

usando il primo metodo funziona bene...
ma verificando i vari 'application.processmessages' l'utente potrebbe navigare nel menu della applicazione e potrebbe attivare altre procedure.

penso che sia necessario evitare che questo accada ed è per questo motivo che, forse, franzauker sovrappone una FormLog alla applicazione 'costringendo' l'utente ad attendere la fine dell'elaborazione o la pressione del tasto 'stop'

per franzauker,
ho letto bene questo problema?
o è meglio lasciare l'utente libero di attivare altre procedure?


grazie

Loading