C# [WPF] WebClients wollen nicht downloaden ...

locomarco

Commander
Dabei seit
Aug. 2009
Beiträge
2.445
Hab einen kleinen Downloadmanager geschrieben dem ich eine beliebige Anzahl von Links geben kann.

Die Links werden in eine Queue<Uri> gesteckt und danach von 4 WebClients Asynchron runtergeladen. Sollten sie jedenfalls.
Der erste WebClient startet den Download, die anderen 3 machen gar nichts. Wenn der erste WebClient mit dem Download fertig ist, wird der nächste Link aus der Queue genommen aber der download startet nicht.

Es wird also genau eine Datei runtergeladen, dann passiert nichts mehr.

Ich hoffe mal einer von euch kann mir dabei Helfen :)

Folgender Code wird in einem Thread ausgeführt:
Code:
public void webClientDownload(object _folder)
        {
            string folder = (string)_folder;

            while (linkQueue.Count > 0 && !isClosing)
            {
                foreach (WebClient webClient in webClients)
                {
                    if (!webClient.IsBusy && linkQueue.Count > 0)
                    {
                        Uri link = linkQueue.Dequeue();
                        HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(link);
                        HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse();

                        if (GetResponseStatus(webResponse) == ResponseStatus.Success)
                        {
                            //header = [attachment;filename="file.ext"]
                            string filename = webResponse.Headers["Content-Disposition"].Split('"')[1];
                            webClient.DownloadFileAsync(link, folder + filename);
                            Dispatcher.Invoke(new Action(delegate(){ 
                                queueCountLabel.Content = linkQueue.Count; 
                            }));
                        }
                        else
                        {
                            MessageBox.Show(GetResponseStatus(webResponse).ToString());
                        }
                    }
                }
            }
        }
 

holy

Lieutenant
Dabei seit
Aug. 2008
Beiträge
533
Der erste WebClient startet den Download, die anderen 3 machen gar nichts. Wenn der erste WebClient mit dem Download fertig ist, wird der nächste Link aus der Queue genommen aber der download startet nicht.
Ich habe deinen Code nicht ausprobiert, aber was du oben geschrieben hast, passt nicht zum geposteten Code ;)
Deine implementierung ist generell nicht sonderlich, ich sag mal, "sauber".

Ersteinmal, wieso ich denke, dass das gepostete nicht mit deiner Erwartung übereinstimmt.

Folgendes Szenario:
  • linkQueue.Count == 4
  • webClients.Count == 4
  • keiner der Clients ist busy

In diesem Fall wird der Rumpf der while-Schleife genau einmal ausgeführt.
Da du 4 WebClients hast, wird jedem dieser Clients ein Link zugeordnet (foreach-Loop) und der Download beginnt sofort. Weiterhin lädst du asynchron runter, d.h. deine Methode returned fast instant.
Die Tatsache, dass das Argument vom Typ object ist (übrigens mit einem absolut fehlplatzierten _) lässt mich vermuten, dass du die Methode als WaitCallback für einen ThreadPoolThread benutzt.
Letzteres ist unnötig wenn du asynchron lädst. Der Thread ist nach beendigung der Methode wieder im Pool und steht anderen Callbacks zur Verfügung.

Wie auch immer. Selbst für den Fall, dass genau nachdem der Thread wieder "frei" wird, GC sich denkt, dass nun ein guter Zeitpunkt wäre mal den Müll einzusammeln, sollten deine Clients downloaden. Du hälst schließlich immer noch eine Referenz auf sie. (Es sei denn, du erzeugst die Clients in der Methode und hast den entsprechenden Code hier gelöscht.)

Weiterhin, der Text des Labels, dem du die Anzahl an Elementen im Queue zuweist, muss nicht unbedingt richtig sein. Da du nirgends blockst, rufst du den Dispatcher 4 mal hinter einander auf. Es gibt keine Garantie dafür, dass der Text in der Reihenfolge der Aufrufe gesetzt wird, nicht bei so kurzen Abständen.

Nichtsdestotrotz, ich gehe mal davon aus, dass du clever genug warst und das Zielverzeichnis nach den Dateien geprüft hast. Wenn sie "fehlen", deutet das daruf, dass der Download fehlgeschlagen ist.
Den Status kannst du über das Event DownloadFileCompleted erfahren.
Code:
webclient.DownloadFileCompleted += OnDownloadCompleted;
Die Signatur ist
Code:
OnDownloadCompleted( object sender, AsyncCompletedEventArgs e )
Im Falle eines Fehlers ist e.Error nicht null.


Nur noch in kurz, was mich generell an deinem Code stört.
Für den Fall
Code:
linkQueue.Count > webClients.Count == true
sieht deine while-Schleife so aus:
Code:
while( true ) ...
Das nennt man Spinning und ist ein richtig gute Möglichkeit, um möglichst viel Prozessor-Zeit zu verschwenden. Du kannst es selber ausprobieren. Füg einfach 5 Links zu irgendwelchen Linux-Isos hinzu und downloade mit 4 Clients. Der Windows-Taskmanager wird dir anzeigen, dass einer deiner CPU-Cores bei 100% läuft.
Eine simple "Lösung" wäre irgendwo in deinem Loop zu blocken, z.B. mit Thread.Sleep( 25 );.

Was du eigentlich machen möchtest ist ein Queue aus Jobs/Downloads, die von einer fixen oder variablen Zahl an WebClients abgearbeitet werden. Es gibt mehrere Möglichkeiten das zu lösen, allerdings keine so simpel wie deine Implementierung ;)

Du brauchst einen Queue mit WebClients und einen mit Downloads. Nun kannst du dir überlegen, wie du weiter vorgehen möchtest.
Wenn du den Overhead mehrer Threads ( je Thread ~1MB) gering halten möchtest, kannst du den ThreadPool benutzen, musst dir dann aber ein Konzept zum Signalisieren überlegen (wann der nächste Thread enqueued werden kann).

Du könntest aber auch einen Producer-Consumer-Queue verwenden. Du erzeugst von vorherein eine fixe Anzahl an Worker-Threads, die darauf warten etwas zu tun.
Hierbei brauchst du keine Queues mehr für die WebClients und Downloads. Stattdessen reihst du Delegates in den PCQ ein. Allerdings musst du in diesem Fall entweder synchron runterladen, da ein Thread ansonsten sofort wieder im Queue landet, oder du benutzt Spinning, aber mit einer relativ hohen Block-Dauer ( 100-250ms ). Letzteres empfiehlt sich hier, da du ausschließen kannst (falls du den WebClient auf diesem Thread erzeugst), dass die Abbruchbedingung durch mehrere Threads manipuliert werden kann.
Code:
WebClient client = new WebClient();
client.DownloadFileAsync( download.Url, path );
while ( client.IsBusy )
{
    Thread.Sleep( 250 );
}
So, genug Text, ich glaube ich habe mein 4-Wochen-Soll erfüllt :D


Edit.
Vergiss bitte was ich über das Label geschrieben habe. Das ist Blödsinn. Der Dispatcher implementiert einen Priority-Queue, und daher egal, wie schnell du ihn aufrufst.
 
Zuletzt bearbeitet:

locomarco

Commander
Ersteller dieses Themas
Dabei seit
Aug. 2009
Beiträge
2.445
WaitCallback? PCQ? ThreadPool?
Hab davon zwar schonmal gehört aber damit kann ich erstmal nichts anfangen.

Ich hab da nur einen Thread und ein Array[4] mit WebClients. Das object als Parameter ist da nur weil ParameterizedThreadStart das so will. Und wegen dem _ bei _folder, mir ist nur kein besser Variablenname eingefallen :p

Also ich hab jetzt mal das HttpWebRequest Zeug entfernt (klappt nämlich nicht immer wie gedacht) und Thread.Sleep(100) in die While-Schleife gesetzt.

Ich habe deinen Code nicht ausprobiert, aber was du oben geschrieben hast, passt nicht zum geposteten Code
Das war das was mein Programm gemacht hat :D

Deine implementierung ist generell nicht sonderlich, ich sag mal, "sauber".
Ich muss dazu sagen, dass ich das alles nur Hobbymäßig mache und ab und zu mal etwas rumprogrammiere. Mein "Wissen" hab ich mir kreuz und quer angelesen aus Tutorials und Code-Schnipseln. Hab das ganze also nicht wirklich Gelernt. Ich besitze ja noch nichtmal ein Buch übers Programmieren.
 
Top