Autor Beitrag
markus490
Hält's aus hier
Beiträge: 4



BeitragVerfasst: Di 08.09.15 23:15 
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:
ausblenden volle Höhe 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
Einloggen, um Attachments anzusehen!
Th69
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4805
Erhaltene Danke: 1061

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: 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).

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


Zuletzt bearbeitet von Th69 am Do 10.09.15 14:26, insgesamt 1-mal bearbeitet

Für diesen Beitrag haben gedankt: markus490
OlafSt
ontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic starofftopic star
Beiträge: 486
Erhaltene Danke: 99

Win7, Win81, Win10
Tokyo, VS2017
BeitragVerfasst: 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 ;)

_________________
Lies, was da steht. Denk dann drüber nach. Dann erst fragen.

Für diesen Beitrag haben gedankt: markus490
markus490 Threadstarter
Hält's aus hier
Beiträge: 4



BeitragVerfasst: 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 Threadstarter
Hält's aus hier
Beiträge: 4



BeitragVerfasst: 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?
ausblenden 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:
ausblenden 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
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starontopic star
Moderator
Beiträge: 4805
Erhaltene Danke: 1061

Win10
C#, C++ (VS 2017/19/22)
BeitragVerfasst: 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 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
Using the BackgroundWorker in C#
Exemplarische Vorgehensweise: Implementieren eines Formulars, das eine Hintergrundoperation verwendet

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 Threadstarter
Hält's aus hier
Beiträge: 4



BeitragVerfasst: 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... ;-)