Entwickler-Ecke

Delphi Language (Object-Pascal) / CLX - Progressbar weiterlaufen lassen bei HTTP-POST?


Christian213 - Di 20.08.13 13:55
Titel: Progressbar weiterlaufen lassen bei HTTP-POST?
Hallo,

ich sende mittels WinInet einen HTTP-Post an einen Server. Dieser wertet den POST aus und gibt dann als Response einen Datenstream, welchen ich downloade.
Nun möchte ich, dass während der Server die Anfrage bearbeitet (also bevor der Response gesendet wird), dass meine Progressbar langsam weiterläuft.
Im Prinzip klappt das auch, allerdings wird beim allen von mir bisher getesteten Timer-Varianten der Timer während des Wartens auch angehalten.
Genau geschieht das bei folgendem Aufruf:

Delphi-Quelltext
1:
HTTPSendRequest(pRequest, nil0, Pointer(POSTDaten), Length(POSTDaten))                    

Im Moment erfolgt der Aufruf synchron, aber auch asynchron bringt nicht die Lösung, da während des Wartens der Callback gar nicht getriggert wird.

Gibt es eine Möglichkeit, dass ein Timer (oder was auch immer) meinen Fortschrittsbalken weiterzeichnet, egal in welchem Zustand sich das Hauptprogramm gerade befindet?

Danke!


jaenicke - Di 20.08.13 14:48

Sende den Request einfach in einem Thread. ;-)


Christian213 - Di 20.08.13 14:51

Hm, die Idee ist gut. Muss ich mich wohl mal näher mit Threads beschäftigen, da habe ich noch etwas Wissenslücken.
Gibt es ein brauchbares Tutorial zu dem Thema?


Christian213 - Do 22.08.13 11:56

Irgendwie hänge ich gerade bei dem Thema Threads. Hier ist mein nicht-funktioneller Versuch:


Delphi-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
20:
21:
22:
23:
24:
25:
26:
27:
28:
29:
30:
31:
32:
33:
34:
35:
36:
37:
38:
39:
40:
41:
42:
43:
44:
45:
46:
47:
48:
49:
50:
51:
52:
function MeineFunktion(A: AnsiString; B: AnsiString; C: AnsiString): AnsiString;
type
  TThreadParams = packed record
    TFunc: TMeineKlasse;
    A: AnsiString;
    B: AnsiString;
    C: AnsiString;
    Response: AnsiString;
    hasFinished : Boolean;
  end;
  PThreadParams = ^TThreadParams;
  var
    tp : TThreadParams;
    Thread : THandle;
    ThreadID : Cardinal;

function ThreadFunc(tp: PThreadParams): Integer;
var
    TFunc: TMeineKlasse;
    A: AnsiString;
    B: AnsiString;
    C: AnsiString;
    Response: AnsiString;
    hasFinished : Boolean;
begin
  A := PThreadParams(tp)^.A; // >>> Hier knallt es bereits - wieso???
  B := PThreadParams(tp)^.B; 
  C := PThreadParams(tp)^.C;
  Response := PThreadParams(tp)^.Response;
  hasFinished := PThreadParams(tp)^.hasFinished;
  Response := TFunc.ExterneFunktion(A, B, C); // Externe Funktion der Klasse "MeineKlasse" in einer anderen Unit
  hasFinished := true; // Soll eigentlich tp.hasFinished setzen, so dass die while-Schleife dies erkennen kann
  Result := 0;
end;

begin
  tp.TFunc := MeineKlasse; // Wurde außerhalb der Funktion schon global definiert
  tp.A := A; // Ebenso diese Variablen
  tp.B := C;
  tp.C := C;
  tp.hasFinished := false;
  ThreadID := 0;
  Thread := BeginThread(nil0, @ThreadFunc, @tp, 0, ThreadID);
  while not tp.hasFinished do
  begin

    // Dinge tun, während der Thread noch läuft, z.B. die Progressbar weiterzeichnen

  end;
  CloseHandle(Thread);
  Result := tp.Response;
end;


Wenn ich den Code kompiliere und starte, bekomme ich einen SIGSEGV.
Kommentiere ich die Zeilen wie "A := PThreadParams(tp)^.A;" aus, passiert dies nicht.
Allerdings komme ich dann auch nicht an die Werte des records ran...
Wo ist mein Fehler?


Blup - Do 22.08.13 12:15

Leite deine eigene Klasse von TThread ab, Properties zur Parameterübergabe, überschreibe "Execute".
Instanz erzeugen, Parameter zuweisen, Ereignis für Beenden zuweisen, Starten.
Falls mit der Progressbar in einer Schleife gespielt wird, ab und zu Prozessmessages aufrufen, damit das Ereignis beim Beenden des Thread durchkommt.
Im Ereignis dann nur ein Variable setzen.
In der Schleife diese Variable auswerten und fertig.


Christian213 - Do 22.08.13 12:52

Sicher könnte ich alles in eine eigene Klasse packen, aber ich wollte den Source nicht unnötig aufblähen.
Die Idee für meinen Ansatz habe ich von hier: http://www.michael-puff.de/Programmierung/Delphi/Tutorials/
In dem ZIP "Threads_mit_Delphi_Demos.zip" gibt ein ein Beispielprojekt "ParameterStack.dpr".
Der Beispielcode dort funktioniert. Nun frage ich mich, was bei meiner Umsetzung falsch ist??


jaenicke - Do 22.08.13 13:15

user profile iconChristian213 hat folgendes geschrieben Zum zitierten Posting springen:
Sicher könnte ich alles in eine eigene Klasse packen, aber ich wollte den Source nicht unnötig aufblähen.
Dann ist TThread genau das richtige, weil du viel weniger Code schreiben musst...
Wenn du alles selbst machst, hast du nur viel mehr Aufwand ohne jeglichen Nutzen...

In aktuellen Delphiversionen wäre auch ein anonymer Thread mit TThread.CreateAnonymousThread möglich.


Blup - Do 22.08.13 13:44

Dein "Threadfunc" ist keine eigenständige Funktion.
Die kann nur im Kontext der übergeordneten Funktion ausgeführt werden, z.B. um auf deren Variablen zugreifen zu können.
Der neue Thread hat natürlich seinen eigenen Stack.
Als eigenständige Funktion sollte das im Prinzip funktionieren.

Delphi-Quelltext
1:
function ThreadFunc(tp: PThreadParams): Integer;                    

Als Member einer Klasse, aber auf Self kann nicht zugegriffen werden:

Delphi-Quelltext
1:
class function ThreadFunc(tp: PThreadParams): Integer; static;