Entwickler-Ecke

WinForms - Simulation Konsumenten-Produzenten-Problem -> Delegate|Event


markus490 - Di 08.09.15 23:15
Titel: Simulation Konsumenten-Produzenten-Problem -> Delegate|Event
Hallo zusammen,

ich möchte folgende Aufgabenstellung lösen:

Simulation eins Konsumenten-Produzenten-Problem.
Es werden zwei Threads erzeugt, wobei der eine der Konsument und der andere der Produzent ist.
Die auszuführenden Methoden produzieren() und entnehmen() werden in der Klasse "Lager" definiert und parallel gestartet.
Es sollen Ausgaben beim Füllen / Entnehmen, sowie bei einem vollen und leeren Lager in einer grafischen Oberfläche (ListBox) erfolgen.

Umgesetzt habe ich das wie folgt:
1. Klasse Lager angelegt, sowie die Methoden entnehmen / produzieren ausprogrammiert.
2. Grafische Oberfläche erzeugt, Screenshot (1) siehe unten.
3. Mittels Delegate + Event versuche ich ListBox-Einträge hinzuzufügen. An dieser Stelle erhalte ich aber eine Exception. Den Fehler kann ich nicht erkennen. Die parallelen Ausgaben auf der Konsole dienen lediglich zur Kontrolle. Screenshot (2) siehe unten

Für jegliche Hilfen bin ich mehr als dankbar.
Eine ausführliche Erklärung wie der Fehler ensteht ist mir sehr wichtig. Ich möchte das Problem verstehen.


Hier mein Quelltext:

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:
163:
164:
using System;
using System.Windows.Forms;
using System.Drawing;
using System.Threading;

namespace GPI13_E_2
{
    class Lager : Form
    {
        int bestand = 500;

        // TEST Delegate
        public delegate void addListBoxEntry(int wert);
        public event addListBoxEntry addEntry;

        public void entnehmen()
        {
            int anz;
            Random anzahl = new Random();

            while (true)
            {
                anz = anzahl.Next(0100);

                if (bestand < anz)
                {
                    Console.WriteLine("<-> Entnahme nicht möglich! Bestand: " + bestand + " versuchte Entnahme: " + anz);
                }
                else
                {
                    lock (this)
                    {
                        bestand -= anz;
                    }
                    Console.WriteLine("<-- Verkaufe Ware. \tAnzahl entnommener Waren: " + anz + "\tNeuer Bestand: " + bestand);
                    //output.Items.Add("<-- Verkaufe Ware. \t\tAnzahl entnommener Waren: " + anz + "\t\tNeuer Bestand: " + bestand);
                    if (addEntry != null)
                    {
                        addEntry(anz);
                    }
                    //     inventory.Text = "Aktueller Bestand:\n" + bestand;
                }
                Thread.Sleep(2000);
            }
        }

        public void produzieren()
        {
            int anz;
            Random anzahl = new Random();

            while (true)
            {
                anz = anzahl.Next(03000);


                if ((bestand + anz) > 10000)
                {
                    Console.WriteLine("<-> Einlagerung nicht möglich. Kapazität (10.000) wird überschritten!)");
                }
                else
                {
                    lock (this)
                    {
                        bestand += anz;
                    }
                    Console.WriteLine("--> Produziere Ware. \tAnzahl Wareneingang: " + anz + "\tNeuer Bestand: " + bestand);
                    //output.Items.Add("--> Produziere Ware. \tAnzahl Wareneingang: " + anz + "\t\t\tNeuer Bestand: " + bestand);
                    if (addEntry != null)
                    {
                        addEntry(anz);
                    }
                    //  inventory.Text = "Aktueller Bestand:\n" + bestand;
                }
                
                Thread.Sleep(3000); 
            }
        }
    }


    class Program
    {
        static ListBox output = new ListBox();
        static Button go = new Button();
        static Lager la = new Lager();

        static void startConsumerProducer(object sender, EventArgs e)
        {
            if (go.Text == "GO!")
            {
                go.Text = "Stopp!";

                Thread en = new Thread(new ThreadStart(la.entnehmen));
                Thread pr = new Thread(new ThreadStart(la.produzieren));
                en.Start();
                pr.Start();
            }
            else
            {
                go.Text = "GO!";
                //Thread stoppen
            }
        }

        // TEST Delegate
        static void add(int wert)
        {
            output.Items.Add(wert);
        }

        static void Main(string[] args)
        {

            // Schriftarten
            Font fontInventory = new Font("Calibri"30, FontStyle.Bold);

            // Listbox zur Ausgabe der Meldungen
            output.Name = "outputListBox";
            output.Location = new Point(50150);
            output.Size = new Size(900400);
            output.BackColor = Color.LightGray;

            // Label zum Anzeigen des aktuellen Lagerbestand
            Label inventory = new Label();
            inventory.Name = "outputLabel";
            inventory.Location = new Point(25025);
            inventory.Size = new Size(500100);
            inventory.BackColor = Color.LightGreen;
            inventory.Font = fontInventory;
            inventory.TextAlign = ContentAlignment.MiddleCenter;
            inventory.Text = "Aktueller Bestand:\n";

            // Start / Stopp
            go.Name = "goButton";
            go.Location = new Point(00);
            go.Size = new Size(5050);
            go.Text = "GO!";
            go.Click += new EventHandler(startConsumerProducer);

            // Clear
            Button clear = new Button();
            clear.Name = "clearButton";
            clear.Location = new Point(500);
            clear.Size = new Size(5050);
            clear.Text = "Clear";
            //clear += new EventHander(clearListBox);

            // Fenster
            la.DesktopLocation = new Point(00);
            la.ClientSize = new Size(1000600);
            la.Text = "Lagerbestand";

            la.Controls.Add(output);
            la.Controls.Add(inventory);
            la.Controls.Add(go);
            la.Controls.Add(clear);
            // TEST Delegate
            la.addEntry += new Lager.addListBoxEntry(add);

            Application.Run(la);
        }
    }
}


Screenshot (1) - GUI
gui

Screenshot (2) - Exception
Laufzeitfehler

Moderiert von user profile iconNarses: Bilder als Anhänge hochgeladen


Th69 - Mi 09.09.15 08:58

Hallo und :welcome:

von einem Workerthread aus darf man nicht direkt auf UI-Elemente zugreifen, sondern muß diese in den GUI-Thread "invoken", s. z.B. Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) [http://www.mycsharp.de/wbb2/thread.php?threadid=33113].

Und für eine Consumer-Producer Queue solltest du (ab .NET 4) die BlockingCollection<> verwenden.


OlafSt - Mi 09.09.15 09:03

Außerdem ist es ganz, ganz mieser Stil, Controls als Variablenspeicher zu benutzen (if go.Text == "Start" ist so ein böses Ding).

Übersetze die Oberfläche mal ins italienische, dann wirst du verstehen ;)


markus490 - Do 10.09.15 13:29

Hätte ich mich doch schon eher hier gemeldet...
Vielen Dank euch beiden für die schnellen und kompetenten Rückmeldungen, das bringt mich wirklich ein großes Stück weiter. :-)


markus490 - Sa 12.09.15 22:59

Hallo zusammen,

ich habe eben versucht das Ganze in meinen obenstehenden Quellcode zu übernehmen.
Leider habe ich dabei noch Schwierigkeiten.

1. Um auf das ouput-Item zuzugreifen (Werte in die Listbox einfügen) muss es ja in der gleichen Klasse sein, in welcher ich die GUI erstellt wird. Korrekt? Sollte ich hierzu den gesamten Quellcode in eine Klasse legen?

C#-Quelltext
1:
output.Items.Add("--> Produziere Ware. \tAnzahl Wareneingang: " + anz + "\t\t\tNeuer Bestand: " + bestand);                    


2. Das Starten meiner Funktion findet wie folgt statt:

C#-Quelltext
1:
Thread pr = new Thread(new ThreadStart(la.produzieren));                    

Soll ich darum noch den ganzen Spaß mit dem "invoke" basteln? Das ist doch bestimmt falsch...


Th69 - So 13.09.15 08:00

Hallo,

den Invoke-Aufruf mußt du (wie dem o.g. Artikel entnommen werden kann) nur um die GUI-Zugriffe (output.Items.Add) packen, darum am besten dafür dann eine eigene Methode erstellen.

Für die Interaktion mit der Form würde sich statt der Threads auch die Klasse BackgroundWorker [https://msdn.microsoft.com/de-de/library/system.componentmodel.backgroundworker%28v=vs.110%29.aspx] anbieten, da diese intern schon die GUI-Synchronisation (mittels bestimmter Ereignisse wie ProgressChanged) übernimmt.
Ein paar Beispiele dazu gibt es u.a. unter
BackgroundWorker Class Sample for Beginners [http://www.codeproject.com/Articles/99143/BackgroundWorker-Class-Sample-for-Beginners]
Using the BackgroundWorker in C# [http://www.dreamincode.net/forums/topic/112547-using-the-backgroundworker-in-c%23/]
Exemplarische Vorgehensweise: Implementieren eines Formulars, das eine Hintergrundoperation verwendet [https://msdn.microsoft.com/de-de/library/b2zk6580%28v=vs.110%29.aspx]

PS: Und dann ist mir noch bei deinem GUI-Code aufgefallen, daß du die GUI-Elemente ja händisch im Code erzeugst. Warum benutzt du dafür nicht den GUI-Designer? Dein Code mittels (teilweiser) static-Variablen ist nämlich keine gute Programmierung.


markus490 - Mo 14.09.15 12:50

funktioniert :-) danke!

Die statischen Bestandteile sind mittlerweile auch alle entfernt.
Kein Designer, da es sich hier um eine Übungsaufgabe aus dem Studium handelt. Sonst hätte ich mir das Leben nicht unnötig schwer gemacht... ;-)