Du bist nicht angemeldet.

Stilllegung des Forums
Das Forum wurde am 05.06.2023 nach über 20 Jahren stillgelegt (weitere Informationen und ein kleiner Rückblick).
Registrierungen, Anmeldungen und Postings sind nicht mehr möglich. Öffentliche Inhalte sind weiterhin zugänglich.
Das Team von spieleprogrammierer.de bedankt sich bei der Community für die vielen schönen Jahre.
Wenn du eine deutschsprachige Spieleentwickler-Community suchst, schau doch mal im Discord und auf ZFX vorbei!

Werbeanzeige

Affje

Treue Seele

  • »Affje« ist der Autor dieses Themas

Beiträge: 89

Beruf: Student

  • Private Nachricht senden

1

12.06.2011, 16:24

[C#] ProgressBar Eventhandler Problem *Update*

Hallo,

ich hab mal wieder eine Frage. Ich habe in meinem Programm jetzt eine Update-Funktion implementiert, diese funktioniert auch recht gut, aber dennoch habe ich ein Problem damit.
Ich benutze dafür einen WebClient und die Methode DownloadFileAsync.
Ich beschreibe vielleicht zuerst mal (den Pfusch), wie ich das gemacht habe:

- Ich schiebe auf meinen Webspace 2 Textdateien, in einer steht die aktuelle Version, in der anderen stehen die neuen Dateien mit dem jeweiligen Pfad z.b. Images/IchBinNeu.png
- Meine Anwendung verbindet sich zur Basisadresse und vergleicht zuerst die lokale Versionsnummer mit der, die ind er Textdatei steht, soweit eigtl. logisch
- Danach, falls die Onlineversion neuer ist, wird die Datei mit den neuen Dateilinks geöffnet, diese werden lokal in eine Textdatei gespeichert.
- Nun öffne ich einen Webclient, der Basisadresse + link öffnet und herunterlädt, ich habe das ganze anfangs mit einer simplen for-Schleife gemacht, da hat DownloadFile gereicht.

Da ich aber eine Fortschrittsanzeige wollte, kam ich um DownloadFileAsync nicht rum, da ich hier einen geeigneten Eventhandler habe. Klappt auch alles ganz gut, aktueller Fortschritt, gesamter Fortschritt usw. wird angezeigt.
Das Problem ist halt jetzt, dass ich das nicht mehr mit einer for-schleife machen kann, da der WebClient mehrere E/A-Anfragen nicht unterstützt, was ja auch sinnvoll ist.
Wenn ich mit WebClient wc = new WebClient(); jedes mal einen neuen erstelle, lädt der natürlich alles schön asynchron runter, aber meine Fortschrittsanzeige spielen natürlich verrückt, da ich ja nur einen EventHandler für 4 gleichzeitige Downloads habe.
Ich rätsle jetzt schon seit einiger zeit, wie ich das File denn asynchron herunterladen kann, um den Eventhandler zu nutzen, aber die Dateien dennoch nacheinander geladen werden.
Ich habe es mit while-Schleife probiert, foreach-Schleife etc, mein letzter Stand ist folgendes:

Im Array links werden die einzelnen Dateilinks gespeichert, len ist einfach eine Abkürzung für links.Length. Die for-Schleife kann ja nicht funktionieren, es sei denn er lädt die Datei schneller runter, als er o inkrementieren kann, was SEHR unwahrscheinlich ist :D
Mit der While-Schleife hängts jedoch.

Quellcode

1
2
3
4
5
6
7
8
9
10
            for (int o = 0; o < len; o++)
            {
                if (!blocked)
                {
                    Data.DownloadFileAsync(new Uri(basic + links[o]), links[o]);
                    o++;
                }
                blocked = true;
            }
            Data.Dispose();


Sobald eine Datei fertig ist, wird der DOwnload wieder freigegeben, wenn ich aber anstatt der for-Schleife, alles in eine While Schleife packe, lädt er zwar, aber das programm hängt logischerweise, wenn blocked = false ist.

Quellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
        void Data_DownloadFileCompleted(object sender, AsyncCompletedEventArgs e)
        {
            files++;
            blocked = false;
            //Sichern der bisherigen Bytes, die empfangen wurden
            temp += received;
            if (files == len)
            {
                listBoxUpdateInfo.Items.Add("Alle Dateien erfolgreich heruntergeladen.");
                listBoxUpdateInfo.SelectedIndex = listBoxUpdateInfo.Items.Count - 1;
                counter();
            }
        }


Wie kann ich das hinbekommen? Wie implementiert man eine Updatefunktion richtig? Das, was ich da gemacht hab, ist sicherlich einfach nur Grütze :D

Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von »Affje« (02.08.2011, 19:48)


Darkrel

Treue Seele

Beiträge: 143

Wohnort: Zürich

Beruf: Student ETH Zürich

  • Private Nachricht senden

2

12.06.2011, 16:48

Also zusammengefasst: Du hast ein Array aus Links auf Dateien und willst die parallel herunterladen?
:cursing:

Affje

Treue Seele

  • »Affje« ist der Autor dieses Themas

Beiträge: 89

Beruf: Student

  • Private Nachricht senden

3

12.06.2011, 16:57

Also zusammengefasst: Du hast ein Array aus Links auf Dateien und willst die parallel herunterladen?
Hallo,
ich habe eine Array mit Dateilinks, die ich herunterladen möchte. Ich Möchte dafür die DownloadFileAsync Methode des Webclienten nutzen, da ich so ProgressBars nutzen kann.
Ich möchte die Dateien aber trotzdem nacheinander und nicht parallel downloaden, da das ja sonst mit dem EventHandler Probleme gibt.
MfG

Darkrel

Treue Seele

Beiträge: 143

Wohnort: Zürich

Beruf: Student ETH Zürich

  • Private Nachricht senden

4

12.06.2011, 17:12

Simple Antwort:
Ein WebClient unterstützt zeitgleich nur eine HTTP Request. (Ich kann dir da keine offizielle Quelle nennen, das ist nur die Standardantwort auf dein Problem wenn man 5 Minuten googelt.)

Benutze entweder mehrere Instanzen des WebClients (und speichere die natürlich so lange bis das event aufgerufen wird) oder verwende keine WebClients.

C#-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
            string[] links = {
                                /* Füge deine links hier ein! */
                             };
            
            WebClient[] clients = new WebClient[links.Length];

            //Definiere den Handler
            System.ComponentModel.AsyncCompletedEventHandler downloadCompletedHandler = (s, e) =>
            {
                if (e.Error != null) Console.WriteLine("Could not download file! {0}", e.Error.Message);
                else
                {
                    KeyValuePair<byte, string> userState = (KeyValuePair<byte, string>)e.UserState;
                    Console.WriteLine("Downloaded file {0}. Download request position: {1}", userState.Value, userState.Key);
                }
                ((WebClient)s).Dispose();
            };

            //Eigentlich müsste hier auch clients.Initialize() gehen. Bei mir sind danach aber alle Clients noch null ;)
            //Habs darum hier runter genommen. Hier werden alle clients initialisiert. Ausserdem wird der eventhandler zugewiesen.
            for (int i = 0; i < clients.Length; ++i)
            {
                clients[i] = new WebClient();
                clients[i].DownloadFileCompleted += downloadCompletedHandler;
            }

            //'count' brauchst du eigentlich garnicht. Ich habe den mal noch hier reingeschraubt um dir zu zeigen, wie du die position
            //an welcher der Download gestartet wurde mitgeben kannst.
            byte count = 0;
            foreach (string link in links)
            {
                string filename =  @"I:\F"+count.ToString()+Path.GetExtension(link);
                clients[count].DownloadFileAsync(new Uri(link), filename, new KeyValuePair<byte, string>(count++, filename));
            }


Du musst natürlich deinen Pfad noch selber anpassen und solltest anstatt von den Arrays Listen oder Dictionaries verwenden.
Du wirst ja nicht immer eine fixe Anzahl downloads haben ;) Kannst also jeden link auf einen WebClient mappen und den entsprechenden WebClienten dann aus dem Dictionary entfernen. Wenn du das aus dem Callback raus machen willst, dann solltest du dich aber mit Threading beschäftigen oder wenn du eine simple, aber nicht ganz so effiziente Lösung willst, die Extensionmethode .AsParallel verwenden. Wenn du noch eine Progressbar willst, dann schau dir mal das entsprechende event im WebClienten an, das lässt sich sicher auch umsetzen.

Alternative Lösung (aber mehr Arbeit): Schreib dir die Funktionalität selber und direkt mit HTTP Requests.

Edit9: Ja, 9 Edits ;) Habe den code nochmal angepasst :p
:cursing:

Dieser Beitrag wurde bereits 10 mal editiert, zuletzt von »Darkrel« (12.06.2011, 17:38)


Affje

Treue Seele

  • »Affje« ist der Autor dieses Themas

Beiträge: 89

Beruf: Student

  • Private Nachricht senden

5

14.06.2011, 17:08

Simple Antwort:
Ein WebClient unterstützt zeitgleich nur eine HTTP Request. (Ich kann dir da keine offizielle Quelle nennen, das ist nur die Standardantwort auf dein Problem wenn man 5 Minuten googelt.)

Benutze entweder mehrere Instanzen des WebClients (und speichere die natürlich so lange bis das event aufgerufen wird) oder verwende keine WebClients.

C#-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
            string[] links = {
                                /* Füge deine links hier ein! */
                             };
            
            WebClient[] clients = new WebClient[links.Length];

            //Definiere den Handler
            System.ComponentModel.AsyncCompletedEventHandler downloadCompletedHandler = (s, e) =>
            {
                if (e.Error != null) Console.WriteLine("Could not download file! {0}", e.Error.Message);
                else
                {
                    KeyValuePair userState = (KeyValuePair)e.UserState;
                    Console.WriteLine("Downloaded file {0}. Download request position: {1}", userState.Value, userState.Key);
                }
                ((WebClient)s).Dispose();
            };

            //Eigentlich müsste hier auch clients.Initialize() gehen. Bei mir sind danach aber alle Clients noch null ;)
            //Habs darum hier runter genommen. Hier werden alle clients initialisiert. Ausserdem wird der eventhandler zugewiesen.
            for (int i = 0; i < clients.Length; ++i)
            {
                clients[i] = new WebClient();
                clients[i].DownloadFileCompleted += downloadCompletedHandler;
            }

            //'count' brauchst du eigentlich garnicht. Ich habe den mal noch hier reingeschraubt um dir zu zeigen, wie du die position
            //an welcher der Download gestartet wurde mitgeben kannst.
            byte count = 0;
            foreach (string link in links)
            {
                string filename =  @"I:\F"+count.ToString()+Path.GetExtension(link);
                clients[count].DownloadFileAsync(new Uri(link), filename, new KeyValuePair(count++, filename));
            }


Du musst natürlich deinen Pfad noch selber anpassen und solltest anstatt von den Arrays Listen oder Dictionaries verwenden.
Du wirst ja nicht immer eine fixe Anzahl downloads haben ;) Kannst also jeden link auf einen WebClient mappen und den entsprechenden WebClienten dann aus dem Dictionary entfernen. Wenn du das aus dem Callback raus machen willst, dann solltest du dich aber mit Threading beschäftigen oder wenn du eine simple, aber nicht ganz so effiziente Lösung willst, die Extensionmethode .AsParallel verwenden. Wenn du noch eine Progressbar willst, dann schau dir mal das entsprechende event im WebClienten an, das lässt sich sicher auch umsetzen.

Alternative Lösung (aber mehr Arbeit): Schreib dir die Funktionalität selber und direkt mit HTTP Requests.

Edit9: Ja, 9 Edits ;) Habe den code nochmal angepasst :p

Danke dir für deine Mühe.
Das problem ist aber, dass ich ja die Fortschrittsanzeige unbedingt beibehalten möchte und das ist bei mehreren Clients, die auf den selben Handler zugreifen einfach nicht realisierbar, oder sehe ich das falsch.
Und natürlich lädt er jetzt die Dateien schön synchron herunter, aber da bin ich wieder genau an dem Punkt wo ich sage: das will ich ja eigentlich nicht wirklich, ich brauch lediglich den

Quellcode

1
DownloadProgressChangedEventHandler

, um Zugriff auf e.BytesReceived etc. zu erhalten, um danach meine progressBar zu richten.

Union_Freedom

Treue Seele

Beiträge: 156

Wohnort: Nähe Hannover

Beruf: Student

  • Private Nachricht senden

6

14.06.2011, 20:41

Wenn ich dich richtig verstanden habe, möchtest du aus einem anderen Thread auf ein Objekt des Hauptthreads zugreifen.

Ich weiß nicht mit was du deine Benutzeroberfläche gestalltest. Mit WPF geht sowas in etwa so

C#-Quelltext

1
2
3
4
5
6
7
8
9
10
11
private void Read_Packet(string s)
        {
            if (textBox3.Dispatcher.CheckAccess())
            {
                textBox3.Text += s;
            }
            else
            {
                textBox3.Dispatcher.BeginInvoke(DispatcherPriority.Send, new Action<string>(Read_Packet), s + "\n");
            }
        }

Danach solltest du vielleicht mal suchen. Dann kannst du deine WebClients in anderen Threads ausführen und jeweilig über den Dispatcher auf dein Steuerelement zugreifen. Ich hoffe das ist das, was du meintest.

mfg
Union_Freedom
Coder bei: http://crushing-gods.de/ (Folgt uns)
Erste Eindrücke zu Crushing Gods Link

Sylence

Community-Fossil

Beiträge: 1 663

Beruf: Softwareentwickler

  • Private Nachricht senden

7

14.06.2011, 20:44

Also so wie ich das verstanden hab musst du dir einfach irgendwo merken, welche Datei aus der Liste du gerade herunter lädst. Wenn diese Datei dann fertig geladen ist, nimmst du die nächste und so weiter.
Also einfach in deinem OnDownloadFileCompleted handler mit dem webclient die nächste datei aus der Liste laden.

Darkrel

Treue Seele

Beiträge: 143

Wohnort: Zürich

Beruf: Student ETH Zürich

  • Private Nachricht senden

8

15.06.2011, 00:30

Hm naja... wie gesagt. Wenn du weisst wie gross alle Dateien gemeinsam sind, kannst du sie auch parallel herunterladen UND eine progressbar haben, die den Gesamtfortschritt anzeigt. Ich schau mir das nachher gleich mal ein wenig an.

Wie schon jemand vor mir gefragt hat: Mit was arbeitest du? Windows Forms, XNA, WPF ... was?
:cursing:

Affje

Treue Seele

  • »Affje« ist der Autor dieses Themas

Beiträge: 89

Beruf: Student

  • Private Nachricht senden

9

15.06.2011, 08:03

Hm naja... wie gesagt. Wenn du weisst wie gross alle Dateien gemeinsam sind, kannst du sie auch parallel herunterladen UND eine progressbar haben, die den Gesamtfortschritt anzeigt. Ich schau mir das nachher gleich mal ein wenig an.

Wie schon jemand vor mir gefragt hat: Mit was arbeitest du? Windows Forms, XNA, WPF ... was?

Forms.

Darkrel

Treue Seele

Beiträge: 143

Wohnort: Zürich

Beruf: Student ETH Zürich

  • Private Nachricht senden

10

15.06.2011, 13:55

C#-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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
public partial class Form1 : Form
    {
        /* Variablen */
        Dictionary<string, Label>           _labels;
        Dictionary<string, ProgressBar>     _progressBars;
        Dictionary<string, Object>          _progressBarsLocks;
        Dictionary<string, WebClient>       _webClients;

        ProgressBar                         _totalProgressBar;
        Object                              _totalProgressBarLock = new Object();

        Button                              _exitButton;

        /* Konstanten */
        const int DLLeft = 10;
        const int DLWidth = 120;
        const int DLHeight = 20;
        const int DLStep = 25;
        const int DLStartTop = 40;

        const int ProgressBarMaxValue = 1000;
        const int ProgressBar_Left = 140;

        const int TotalProgressBarTop = 200;
        const int TotalProgressBarLeft = 10;
        const int TotalProgressBarWidth = 270;

        /* Konstruktor */
        public Form1()
        {
            InitializeComponent();
            _exitButton = new Button();
            _exitButton.Text = "Exit";
            _exitButton.Click += new EventHandler(
                (sender, args) =>
                {
                    this.Close();
                }
                );
            _exitButton.Left = TotalProgressBarLeft;
            _exitButton.Width = TotalProgressBarWidth;
            _exitButton.Top = 5;
            Controls.Add(_exitButton);
            _exitButton.Show();

            string[] links = {
                                /* Deine Links hier */
                            };
            StartDownload(links);

            this.FormBorderStyle = FormBorderStyle.None;
        }


        /* Startet den Download aller dateien gleichzeitig und Asynchron.
         * Für jede Datei, die heruntergealden wird, wird eine eigene Progressbar angezeigt.
         * Zusätzlich wird der Gesamstatus in einer zusätzlichen Progressbar angezeigt.
         * 
         * Anmerkung:   Das ist kein schöner Code ;) Das Beispiel soll nur vermitteln, wie dein
         *              Problem gelöst werden _könnte_. */
        public void StartDownload(IEnumerable<string> links)
        {
            /* Die Progressbar initialisieren, die den Gesamstatus anzeigt. */
            _totalProgressBar = new ProgressBar();
            _totalProgressBar.Maximum = ProgressBarMaxValue * links.Count();
            _totalProgressBar.Top = TotalProgressBarTop;
            _totalProgressBar.Left = TotalProgressBarLeft;
            _totalProgressBar.Width = TotalProgressBarWidth;
            _totalProgressBar.Height = DLHeight;
            this.Controls.Add(_totalProgressBar);
            _totalProgressBar.Show();

            _progressBarsLocks = new Dictionary<string, Object>();
            _progressBars = new Dictionary<string, ProgressBar>();
            _webClients = new Dictionary<string, WebClient>();
            _labels = new Dictionary<string, Label>();

            // Wird benutzt, um die Position der labels und progress bars zu setzen.
            int Top = DLStartTop;

            foreach (string link in links)
            {
                //Da ich hier der einfachheithalber mit locks gearbeitet habe, (das ist sicher nicht optimal, wie gesagt,
                //ich würde mir da mal ein Threadingbuch zu gute führen) verwende ich sicherheitshalber für jede Progressbar einen
                // eigenen Lock.
                _progressBarsLocks.Add(link, new Object());

                //Progressbar für jede Datei die heruntergeladen wird initialisieren.
                ProgressBar pb = new ProgressBar();
                pb.Top = Top;
                pb.Width = DLWidth;
                pb.Height = DLHeight;
                pb.Left = ProgressBar_Left;
                pb.Maximum = ProgressBarMaxValue;
                _progressBars.Add(link, pb);

                //Label für jede Datei die heruntergeladen wird initialisieren.
                Label label = new Label();
                label.Text = Path.GetFileName(link);
                label.Left = DLLeft;
                label.Width = DLWidth;
                label.Height = DLHeight;
                label.Top = Top;
                label.ForeColor = Color.Red;
                _labels.Add(link, label);

                //Jede Datei braucht einen eigenen WebClienten.
                WebClient wc = new WebClient();
                _webClients.Add(link, wc);

                this.Controls.Add(pb);
                this.Controls.Add(label);

                label.Show();
                pb.Show();

                Top += DLStep;

                //Handler für den Fall, dass eine Datei vollständig heruntergeladen wurde.
                //Falls dir die schreibweise nicht bekannt ist, mach dich mit Lambda expressions vertraut.
                wc.DownloadFileCompleted += new AsyncCompletedEventHandler(
                    (sender, args) =>
                    {
                        string link_to_file = args.UserState as string;

                        Label progressBarLabel = _labels[link_to_file];
                        progressBarLabel.ForeColor = Color.Green;


                        ProgressBar progressBar = _progressBars[link_to_file];
                        Object progressBarLock = _progressBarsLocks[link_to_file];
                        lock (progressBarLock){ progressBar.Value = progressBar.Maximum; }

                        WebClient webClient = _webClients[link_to_file];
                        webClient.Dispose();
                        _webClients.Remove(link_to_file);

                        lock (_totalProgressBarLock) { UpdateTotalProgressBar(); }
                    }
                    );

                //Anpassen der Progressbars.
                wc.DownloadProgressChanged += new DownloadProgressChangedEventHandler(
                    (sender, args) =>
                    {
                        string link_to_file = args.UserState as string;
                        ProgressBar this_pb = _progressBars[link_to_file];
                        this_pb.Value = Convert.ToInt32((ProgressBarMaxValue / args.TotalBytesToReceive) * args.BytesReceived);
                        lock (_totalProgressBarLock) { UpdateTotalProgressBar(); }
                    }
                    );

                string fileName = Path.GetFileName(link);
                wc.DownloadFileAsync(new Uri(link), fileName, link); 
            }
        }

        private void UpdateTotalProgressBar()
        {
            _totalProgressBar.Value = _progressBars.Sum((kvp) => kvp.Value.Value);
        }
    }


Ich hoffe mal, das ist das was du haben wolltest. Eine Progressbar, die den Gesamtstatus anzeigt, auch wenn die files synchron heruntergeladen werden.
Wenn du die einzelnen Progressbars nicht brauchst, dann ersetz das entsprechende dictionary einfach durch ein Dictionary<string, long> _progress; oder wie auch immer du es nennen willst und anstatt this_pb.Value = ... machst du dann _progress[link_to_file] = ...

Natürlich musst du dann auch noch die lambda expression in "Sum" anpassen.

Oh und... Locks sind nicht wirklich eine schöne Lösung. Wie in den Comments schon geschrieben, da solltest du Ersatz finden.

Edit: Ich habe dir mal noch ein kleines Beispiel angehängt. Einfach in eine *.txt datei einen Link eintragen - schon werden alle PDFs die im Quelltext angegeben sind heruntergeladen. Asynchron und mit deiner heissgeliebten Progressbar :p
»Darkrel« hat folgende Datei angehängt:
:cursing:

Dieser Beitrag wurde bereits 7 mal editiert, zuletzt von »Darkrel« (15.06.2011, 16:33)


Werbeanzeige