WinHttpRequest Objekt Ressourcenfreigabe

11/06/2009 - 19:50 von Ulrich Korndoerfer | Report spam
Hallo NG,

ich benutze das WinHttpRequest-Objekt der WinHTTP Services V5.1
(winhttp.dll) in einem VB6 Programm unter WIN XP, um asynchron bis zu
ca. 60 Bilder (PNGs) gleichzeitig (parallel) von einem Server im
Internet zu laden.

Die Bilder kommen nach dem Download in einen Cache (2 stufig: kleinerer
InMemory Cache für ca. 1000 Bilder, beliebig großer Cache auf Platte).
Ein Teil der gerade geholten Bilder wird auch sofort in einem Fenster
angezeigt. Der User kann im Fenster in alle Richtungen scrollen, dann
müssen neue Bilder angezeigt werden, die entweder aus dem Cache oder neu
vom Server geholt werden.

Prinzipiell könnte der Download auch seriell erfolgen (nur eine Instanz
des WinnHTppRequest-Objektes, Bild für Bild hintereinander). Da der
Http-Transfer einen gewissen Zeitoverhead hat und der Server für das
Bereitstellen und Ausliefern der Bilder auch etwas Zeit braucht, kann
ich allerdings durch das parallele Holen den gesamt benötigten
Zeitaufwand auf ca. 1/3 senken.

Aktuell wird für jedes zu holende Bild eine Workerklasse instantiiert
(die im Thread der Anwendung làuft), für 60 Bilder werden also in einem
Rutsch 60 Instanzen einer Workerklasse erzeugt und deren Startmethode
aufgerufen. Dies geschieht in einer Methode StartDownloads, die
ebenfalls im Thread der Anwendung làuft. Sie setzt noch ein Flag
(Downloading) und einen Zàhler. Nach Erzeugung der Instanzen und Aufruf
der Startmethode beendet diese Methode StartDownloads.

Die Startmethode der Workerklasse erhàlt beim Aufruf die nötigen Daten
für den Download (hauptsàchlich die URL), instantiiert ein
WinHttpRequest-Objekt (das per WithEvents in die Workerklasse
eingebunden ist), verhindert ihre (die der Workerklasse) Zerstörung, in
dem sie einen Verweis auf sich selbst in einer Membervariablen der
Workerklasse setzt, startet den asynchronen Download über die Send
Methode des WinHttpRequest Objektes und kehrt zurück.

Danach tickern die angeforderten Daten ein, werden in den Workerklassen
in einem vom WinHttpRequest-Objekt geworfenen Event (OnResponseFinished)
abgeholt und per Callback an den eigentlichen Datenempfànger
weitergeleitet. Der dekrementiert den Zàhler, stopft die Daten in den
Cache und zeigt sie, falls nötig, an. Dann löscht sie den Verweis auf
sich selbst (das alles passiert in dem Event). Dadurch wird die Instanz
der Workerklasse zerstört, damit wird auch die Referenz auf die erzeugte
WinHttpRequest-Instanz gelöscht und das WinHttpRequestObjekt sollte sich
damit auch zerstören (zumindest ist das das Standardverhalten bei
COM-Objekten).

Wenn alle Worker beendet sind (durch erfolgreichen Download oder wegen
Fehler), wird das Downloading-Flag wieder auf False gesetzt. Das GUI
bleibt wàhrend der ganzen Downloaderei "responsive", sperrt aber alle
Aktionen, die einen erneuten Download anstossen würden.

Funktioniert alles ganz pràchtig, und ich würde auch nicht weiter
nachfragen, wenn ich nicht neulich mit einem Tool (TCPView von
Sysinternals) mir mal angesehen hàtte, was sich auf der TCP Seite so tut.

Mein Programm wird gestartet und es werden zB 60 Bilder angefordert. Es
werden erstmal 60 Connections zum Server erzeugt. Na ja, das war ja zu
erwarten. Aber: Selbst nachdem alle Downloads beendet wurden und die
WinHttpRequest-Objekte damit zerstört wurden, bleiben die Connections
bestehen!

Schlimmer noch: werden nun nochmals Bilder angefordert, werden
zusàtzlich neue Connections erzeugt! Allerdings wàchst die Anzahl der
Connections bei weiteren Downloads nicht ins Unendliche. Es gibt eine
Obergrenze, die nicht überschritten wird, und die nur minimal größer
(ca. 1 - 3 Connections) als die Anzahl der jemals gleichzeitig
angeforderten Downloads ist. Also: wurden zB wàhrend eines
Programmlaufes nie mehr als gleichzeitig 6 Downloads angefordert, ist
die Anzahl der bestehenden Connections auch nie größer als ca. 9.

Es scheint so zu sein, als ob die Connections nie geschlossen würden,
sondern bei Bedarf (obwohl die WinHttpRequest Objekte stets aufs neue
erzeugt und wieder zerstört werden) wieder verwendet werden. Also:
werden X WinHttpRequest Objekte erzeugt, werden entweder:

- bei bereits vorhandenen X Connections diese wieder verwendet, aber ab
und zu trotzdem zusàtzlich max 1 - 3 neue Connections erzeugt
- bei weniger als X vorhandenen Connections die zusàtzlich nötigen erzeugt.

Ok, es gibt noch eine kleine Komplikation: eigentlich verbinde ich mit 4
verschiedenen Servern, die per "Round Robin" ausgewàhlt werden. Dadurch
wird eine bessere Lastverteilung auf Serverseite ermöglicht.
Möglicherweise ist es, wenn ich mich nur mit einem Server verbinden
würde, so, daß dann stets nur die vorhandenen Connections
wiederverwendet werden und, wenn mehr Connections angefordert werden,
als bereits vorhanden, dann nur exakt soviele neue Connections erzeugt
werden, wie benötigt.

Unterm Strich ergibt sich folgendes Verhalten:

- Für jede neu angelegte und zum Download verwendete HttpRequest-Instanz
wird entweder eine neue Connection angelegt oder eine bereits bestehende
wiederverwendet.
- Wird die Instanz beendet, bleibt die Connection bestehen. Erst wenn
die Applikation beendet wird, werden alle Connections beendet.

Fragen an die NG:

- Ist dieses Verhalten normal? Oder mache ich was falsch. Evtl. bediene
ich das WinHttpRequest-Objekt ja falsch. Es könnte zB vielleicht möglich
sein, dem Objekt vor seiner Zerstörung noch den Auftrag zu geben, die
zugehörige Connection zu löschen.

- Stören diese wàhrend der Laufzeit des Programmes beibehaltenen
Connections überhaupt? Also stört oder belastet das den Server in
irgendeiner Form, oder den eigenen Rechner (zB. in puncto Ressourcen)

- Bis jetzt zerstöre ich die WinHttpRequest-Objekte jedesmal, weil ich
dachte, damit alle Ressourcen freizugeben. Das war mir wichtig, weil
nach einer gewissen Zeit der Cache gut gefüllt ist und damit wàhrend der
(evtl. langen) Laufzeit des Programmes nur noch ab und zu Downloads
angefordert werden. Wenn aber durch das Zerstören der Objekte zumindest
die angeforderten Connections eh nicht beendet werden und das
Beibehalten der Connections wàhrend der Laufzeit nicht nennenswert
stört, könnte ich in Zukunft einmal erzeugte Objekte beibehalten und
wieder verwenden. Ich würde dann auch ein Objektpooling implementieren:
es würden nie mehr als zB 6 Instanzen gleichzeitig existieren, und bei
zB 60 zu holenden Bildern dies dann halt geeignet auf die 6 Instanzen
aufteilen.

Ach ja, eins noch (nur so am Rande): aktuell werden der Einfachheit
halber in den Diskcache (ein Ordner im Dateisystem) alle Dateien einzeln
abgelegt. Da kommen schnell mal einige zehntausend bis hunderttausend
Dateien zusammen. Mit zunehmender Dateianzahl (ab roundabout 10000 wirds
kritisch) verhaspelt sich XP aber zusehends, bis hin zum Brownout:

- meine App stürzt ab (Absturz ist eigentlich falsch, sie wird einfach
kommentarlos beendet)
- der Desktop braucht eine Ewigkeit (im mehrere Sekundenbereich), um
sich wieder aufzubauen
- der Explorer hàngt lange

Das XP Probleme bekommt, wenn die Anzahl der Dateien in einem Ordner zu
groß wird, wußte ich schon bzw. habe ich schon früher des öfteren
"erlitten".

Deshalb (das war von Anfang an so geplant, ist aber bis jetzt noch nicht
realisiert) werden in der nàchsten Version des Programmes mehrere
einzelne Bilder in einer Datei zusammengefasst, um damit die Anzahl der
Dateien pro Ordner zu reduzieren. Ich bin gerade dabei, abzuschàtzen,
wieviele Einzelbilder ich in jeweils einer Datei zusammenfassen sollte.

Dazu müßte ich wissen, ab welcher Anzahl von Dateien pro Ordner es
kritisch wird. Wie gesagt, nach meiner Erfahrung ist 10000 so die
Obergrenze (wahrscheinlich sogar eher niedriger). Ich wüßte gerne Eure
Meinung dazu.

Ulrich Korndoerfer

VB tips, helpers, solutions -> http://www.proSource.de/Downloads/
 

Lesen sie die antworten

#1 Ulrich Korndoerfer
11/06/2009 - 20:19 | Warnen spam
Ups,

Ulrich Korndoerfer schrieb:


Aktuell wird für jedes zu holende Bild eine Workerklasse instantiiert
(die im Thread der Anwendung làuft), für 60 Bilder werden also in einem
Rutsch 60 Instanzen einer Workerklasse erzeugt und deren Startmethode
aufgerufen. Dies geschieht in einer Methode StartDownloads, die
ebenfalls im Thread der Anwendung làuft. Sie setzt noch ein Flag
(Downloading) und einen Zàhler. Nach Erzeugung der Instanzen und Aufruf
der Startmethode beendet diese Methode StartDownloads.




Besser:

Aktuell wird für jedes zu holende Bild eine Workerklasse instantiiert
(die im Thread der Anwendung làuft), für 60 Bilder werden also in einem
Rutsch 60 Instanzen einer Workerklasse erzeugt und deren Startmethode
aufgerufen. Dies geschieht in einer Methode StartDownloads, die
ebenfalls im Thread der Anwendung làuft. Sie setzt noch ein Flag
(Downloading) und einen Zàhler. Nach Erzeugung der Instanzen und Aufruf
derer Startmethode beendet StartDownloads.

:-)

Ulrich Korndoerfer

VB tips, helpers, solutions -> http://www.proSource.de/Downloads/

Ähnliche fragen