Forum: PC-Programmierung Anwendung friert ein beim schließen


von Andreas L. (andreas_l441)


Lesenswert?

Hallo,
ich habe das Problem, dass wenn ich in meiner Anwendung einen SerialPort 
erstelle und darüber Daten einlese und anschließend das Fenster 
schließe, das Programm hängen bleibt. Es lässt sich dann nur über den 
Taskmanager schließen. Anschließend kommt folgender Fehler:

"Ein Ausnahmefehler des Typs "System.ObjectDisposedException" ist in 
System.Windows.Forms.dll aufgetreten."

Das tritt nur auf, wenn der Port einmal offen ist.
Achja, ich arbeite mit Visual C#. Hier noch der Code, leider ein 
bisschen unübersichtlich:
1
using System;
2
using System.Collections.Generic;
3
using System.ComponentModel;
4
using System.Data;
5
using System.Drawing;
6
using System.Linq;
7
using System.Text;
8
using System.Threading.Tasks;
9
using System.Windows.Forms;
10
using System.IO.Ports;
11
using CommandsPD4I;
12
13
namespace CSharpExample
14
{
15
    public partial class CSharpExample : Form
16
    {
17
        public ComMotorCommands motor1;
18
        
19
        int aktuellePosition = 0;
20
        int kraft = 0;
21
        int kraftOffset = 0;
22
        int maxKraft = 600;
23
        int modusValue = 0;
24
        int ose = 0;
25
        int originalKraft = 0;
26
        int startpos = 0;
27
        int use = 6000;
28
        int zyklenAnzahl = 1;
29
        int zyklusCount = 0;
30
        bool zyklusLaeuft = false;
31
32
        public CSharpExample()
33
        {
34
            InitializeComponent();
35
            motor1 = new ComMotorCommands();
36
37
            //Comboboxen befüllen 
38
            foreach (var item in SerialPort.GetPortNames())  
39
            {
40
                cbArduinoConnect.Items.Add(item);
41
                cbMotorConnect.Items.Add(item);
42
            }
43
            cbArduinoConnect.Text = cbArduinoConnect.Items[0].ToString();
44
            cbMotorConnect.Text = cbArduinoConnect.Items[0].ToString();
45
        }
46
47
        delegate void InvokeLB(string Data);
48
        InvokeLB lbRecievedDelegate;
49
50
        private void btnArduinoVerbinden_Click(object sender, EventArgs e)
51
        {
52
            arduino.PortName = cbArduinoConnect.Text;
53
            arduino.Open();
54
            if (arduino.IsOpen)
55
            {
56
                lblArduinoConnectStatus.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(200)))), ((int)(((byte)(0)))));
57
                lblArduinoConnectStatus.Text = "Arduino verbunden mit " + arduino.PortName;
58
            }
59
            lbRecievedDelegate = new InvokeLB(InvokeLBRecieved);
60
            arduino.DataReceived += new SerialDataReceivedEventHandler(arduino_DataReceived); //DataRecieved Event abonnieren
61
        }
62
63
        private void arduino_DataReceived(object sender, SerialDataReceivedEventArgs e)
64
        {
65
            string RecievedLine = " ";
66
            while (RecievedLine != "")
67
            {
68
                RecievedLine = arduino.ReadLine();
69
                lblKraft.Invoke(lbRecievedDelegate, new object[] { RecievedLine });
70
            }
71
        } 
72
73
        void InvokeLBRecieved(string Data)
74
        {
75
            string origKraft = Data;
76
            Int32.TryParse(origKraft, out originalKraft);
77
            toolStripStatusLabelKraft.Text = Convert.ToString(originalKraft - kraftOffset);
78
            kraft = originalKraft - kraftOffset;
79
            if (kraft >= maxKraft && cbZyklenmodi.Text == "Max. Kraft [N]:" && zyklusLaeuft)
80
            {
81
                motor1.StopTravelProfile();
82
                motor1.SetPositionType(2);
83
                motor1.SetSteps(startpos);
84
                motor1.SetMaxFrequency(1000);
85
                motor1.StartTravelProfile();
86
                zyklusLaeuft = false;
87
            }
88
        }
89
90
        private void handsteuerungOeffnen(object sender, EventArgs e)
91
        {
92
            Handsteuerung handsteuerung = new Handsteuerung(motor1);
93
            handsteuerung.Show();
94
        }
95
96
        private void motorVerbinden(object sender, EventArgs e)
97
        {
98
            //Motoreinstellungen
99
            motor1.SelectedPort = cbMotorConnect.Text;
100
            motor1.Baudrate = 115200;
101
            motor1.MotorAddresse = 1;
102
            if (motor1.ErrorFlag)
103
            {
104
                lblMotorConnectStatus.Text = motor1.ErrorMessageString;
105
            }
106
            else
107
            {
108
                lblMotorConnectStatus.ForeColor = System.Drawing.Color.FromArgb(((int)(((byte)(0)))), ((int)(((byte)(200)))), ((int)(((byte)(0)))));
109
                lblMotorConnectStatus.Text = "Motor verbunden mit " + motor1.SelectedPort;
110
                timer.Enabled = true;
111
                toolStripStatusLabelPos.Text = Convert.ToString(motor1.GetPosition());
112
            }
113
        }
114
115
        private void timer_Tick(object sender, EventArgs e)
116
        {
117
            aktuellePosition = motor1.GetPosition();
118
            toolStripStatusLabelPos.Text = Convert.ToString(motor1.GetPosition());
119
            lblZyklusCount.Text = Convert.ToString(zyklusCount);
120
            chart1.Series["Kraft"].Points.AddXY(aktuellePosition, kraft);
121
            if (aktuellePosition == startpos)
122
            {
123
                if (zyklusCount > 0 && zyklusCount < zyklenAnzahl)
124
                {
125
                    startZyklus();
126
                }
127
                else if (zyklusCount == zyklenAnzahl)
128
                {
129
                    zyklusCount = 0;
130
                }
131
            }
132
            if (aktuellePosition == modusValue && cbZyklenmodi.Text == "Position:" && zyklusLaeuft)
133
            {
134
                motor1.SetPositionType(2);
135
                motor1.SetSteps(startpos);
136
                motor1.SetMaxFrequency(1000);
137
                motor1.StartTravelProfile();
138
                zyklusLaeuft = false;
139
            }
140
        }
141
142
        private void toolStripBtnStartposUebernehmen_Click(object sender, EventArgs e)
143
        {
144
            startpos = motor1.GetPosition();
145
        }
146
147
        private void toolStripBtnStartPos_Click(object sender, EventArgs e)
148
        {
149
            motor1.SetSteps(startpos);
150
            motor1.SetMaxFrequency(1000);
151
            motor1.SetPositionType(2);
152
            motor1.StartTravelProfile();
153
        }
154
155
        private void toolStripBtnStop_Click(object sender, EventArgs e)
156
        {
157
            motor1.StopTravelProfile();
158
            chart1.Series["Kraft"].Points.Clear();
159
            zyklusCount = 0;
160
        }
161
162
        private void toolStripBtnZurueck_Click(object sender, EventArgs e)
163
        {
164
            motor1.SetMaxFrequency(1000);
165
            motor1.SetDirection(0);
166
            motor1.SetPositionType(4);
167
            motor1.StartTravelProfile();
168
        }
169
170
        private void btnUebernehmen_Click(object sender, EventArgs e)
171
        {
172
            if (Int32.TryParse(tbOSE.Text, out ose)           && Int32.TryParse(tbUSE.Text, out use) && 
173
                Int32.TryParse(tbModus.Text, out maxKraft)    && Int32.TryParse(tbZyklenAnzahl.Text, out zyklenAnzahl) &&
174
                Int32.TryParse(tbModus.Text, out modusValue))
175
            {
176
                MessageBox.Show("Werte wurden übernommen!");
177
            }
178
            else
179
            {
180
                MessageBox.Show("Fehler!");
181
            }  
182
        }
183
184
        private void toolStripBtnKraft_0_Click(object sender, EventArgs e)
185
        {
186
            kraftOffset = originalKraft;
187
        }
188
189
        private void toolStripBtnStart_Click(object sender, EventArgs e)
190
        {
191
            startZyklus();
192
        }
193
194
        private void startZyklus()
195
        {
196
            motor1.SetMaxFrequency(100);
197
            motor1.SetDirection(1);
198
            if (cbZyklenmodi.Text == "Max. Kraft [N]:")
199
            {
200
                motor1.SetPositionType(5);
201
            }
202
            else if (cbZyklenmodi.Text == "Position:")
203
            {
204
                motor1.SetPositionType(2);
205
206
                int modus;
207
                if (Int32.TryParse(tbModus.Text, out modus))
208
                {
209
                    motor1.SetSteps(modus);
210
                }
211
                else
212
                {
213
                    MessageBox.Show("Falsche Eingabe!");
214
                }
215
                
216
            }
217
            motor1.StartTravelProfile();
218
            zyklusLaeuft = true;
219
            chart1.Enabled = true;
220
            zyklusCount++;
221
        }
222
223
        private void beendenToolStripMenuItem_Click(object sender, EventArgs e)
224
        {
225
            this.Close();
226
        }
227
228
        
229
    }
230
}

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Wusstest Du, daß das Visual Studio, das Du zum Erstellen Deines 
C#-Programmes verwendest, auch einen Debugger enthält?

von Karl H. (kbuchegg)


Lesenswert?

Andreas L. schrieb:

> "Ein Ausnahmefehler des Typs "System.ObjectDisposedException" ist in
> System.Windows.Forms.dll aufgetreten."
>
> Das tritt nur auf, wenn der Port einmal offen ist.


Da würde ich doch glatt als Arbeitshypothese annehmen, dass an der 
Reihenfolge beim Aufräumen etwas nicht stimmt.

Meine Arbeitshypothese wäre, dass versucht wird einen Eventhandler (das 
Port Objekt drängt sich da förmlich auf) für ein Objekt aufzurufen, das 
schon zerstört wurde.


Das ist halt die Krux in einer Sprache mit Garbage Collection. Man macht 
sich keinerlei Gedanken mehr darüber, dass irgendwer auch hinter einem 
aufräumen muss und das es dafür oft sinnvolle Reihenfolgen gibt.

von c-hater (Gast)


Lesenswert?

Karl H. schrieb:

> Da würde ich doch glatt als Arbeitshypothese annehmen, dass an der
> Reihenfolge beim Aufräumen etwas nicht stimmt.

So ist es höchstwahrscheinlich auch. Allerdings anders, als du denkst...

> Meine Arbeitshypothese wäre, dass versucht wird einen Eventhandler (das
> Port Objekt drängt sich da förmlich auf) für ein Objekt aufzurufen, das
> schon zerstört wurde.

Das könnte eigentlich nicht passieren, weil der Handler ja nur in 
einer vom lebenden Objekt selber ausgelösten Ereignisbehandlung 
aufgerufen wird.

Das "eigentlich" ist aber genau der springende Punkt. Der Code von 
System.IO.Ports.Serialport ist leider ganz allgemein katastrophal mies, 
insbesondere ist er aber nicht vollständig threadsicher. Die 
Winzigweich-Spacken haben in der Geschichte von .Net obendrein mehrfach 
unsachgerecht dran rumgefrickelt, um die schlimmsten Probleme zu 
"beheben", so daß eine Anleitung zur Behebung des Problems die genaue 
Versionsangabe des verwendeten Frameworks erfordert, denn der Workaround 
muß an die jeweils aktuellen Winzigweich-Fehler angepaßt sein...

Übrigens: Wenn es sich bei dem verwendeten Port um einen virtuelle Port 
handelt (z.B. USB), multiplizieren sich die Probleme noch einmal. So ein 
Ding kann nämlich auch mal einfach so verschwinden (z.B. wenn jemand den 
Stecker zieht). Bis .Net 4 warf der Winzigweich-Müll in diesem Fall eine 
"uncatchable" Exception, d.h.: die Anwendung stürzte unrettbar ab und es 
gab kein Mittel, um das zu verhindern. Bei .Net 4 haben dann die 
Winzigweich-Spacken mal wieder eins ihrer tollen Bugfixes gemacht, was 
dazu führt, daß es zwar nicht mehr zu diesem Super-GAU kommt, aber dafür 
die Anwendung im Wald steht, auch wenn der COM-Port wieder "da" ist. 
Schlicht und einfach deshalb, weil sie nunmehr garnicht mehr bemerken 
kann, daß er mal "weg" war...

Superpeinlich, weil man ja mittlerweile die .Net-Quelltexte auch als 
Normalsterblicher problemlos einsehen kann...

Nö, den Trouble will man sich auf Dauer nicht antun. Es gibt mehrere 
SerialPort-Klassen von 3rd-Party-Anbietern, die zeigen, wie man sowas 
richtig umsetzt. Die setzen genauso wie MS auf's WinAPI auf, machen das 
aber im Gegensatz zu MS einfach mal nur richtig.

Ich persönlich benutze allerdings meine eigene Klasse, die schon zu 
.Net1.1-Zeiten entstanden ist, damals noch nicht deswegen, weil die 
MS-Umsetzung so furchtbar verbugged war, sondern deshalb, weil es vor 
.Net2.0 die Serialport-Klasse überhaupt noch nicht gab. Das hat 
allerdings auch die Folge, daß meine ein völlig davon abweichendes 
"Interface" hat.

Auf die o.g. 3rd-Party-Klassen trifft das nicht zu. Die sind 
(offensichtlich nach .Net2.0) mit dem Ziel entwickelt worden, möglichst 
kompatibel zum obermiesen "Original" zu sein, aber dessen abstruse 
Fehler zu vermeiden.

> Das ist halt die Krux in einer Sprache mit Garbage Collection.

Die taucht zwar in einigen KB-Artikeln in diesem Zshg. auf, hat aber de 
facto mit den SerialPort-Problemen rein garnix zu schaffen. Die Existenz 
dieser KB-Artikel zeigt eigentlich nur sehr deutlich, wie sehr die 
"Entwickler" bei MS eigentlich im Nebel des unverstandenen Codes 
stochern. Und diese Typen dürfen garantiert den gesamten .Net-Code 
(incl. Marshalling!!!) einsehen und wahrscheinlich auch wesentlich mehr 
Code des Win-API, als er mir und den anderen 3rd-Parties zur Verfügung 
steht...

Wahrscheinlich irgendwelche billigen Inder, die nur C# können...

MS muß sich wohl derzeit auf so grundlegende Probleme wie die 
Entwicklung von "gekachelten" Benutzeroberflächen konzentrieren. Da 
bleibt für Randprobleme wie funktionierenden Basiscode wohl echt wenig 
Zeit...

von Matthias L. (Gast)


Lesenswert?

c-hater schrieb:
> MS muß sich wohl derzeit auf so grundlegende Probleme wie die
> Entwicklung von "gekachelten" Benutzeroberflächen konzentrieren. Da
> bleibt für Randprobleme wie funktionierenden Basiscode wohl echt wenig
> Zeit...

Leider scheint das auch für Googles android zu gelten...

von Andreas L. (andreas_l441)


Lesenswert?

Hmm.. habt ihr eine Idee wie ich das Problem lösen könnte?
Ich weiß leider garnicht so genau, was Eventhandler sind.. Den Code zum 
Daten empfangen über den SerialPort habe ich aus dem Netz übernommen.
Wenn da das Problem liegt wüsste ich nicht so genau wie das zu 
verhindern ist.

von Noch einer (Gast)


Lesenswert?

Ach - halb so wild. Verstehst ja inzwischen schon einiges - kannst ja 
die Klasse in dein Programm einbauen. In den Debugger einarbeiten und 
Tutorials für solche Konzepte durcharbeiten ist auch nicht mehr der 
große Aufwand.

Wenn wir hier im Nebel stochern und du alle Tipps ausprobierst - das 
dauert länger.

von Karl H. (kbuchegg)


Lesenswert?

c-hater schrieb:

> facto mit den SerialPort-Problemen rein garnix zu schaffen.

Danke für die Aufklärung. Ich bin in der glücklichen Lage, das .Net 
Geraffel links liegen lassen zu können. Ja, ich weiss: ich manövriere 
mich damit selbst ein wenig ins Abseits.
Mein einziger Ausflug zu C# (Pflege eines existierenden Programms) hat 
mir allerdings auch gezeigt, das Garbage Collection auch nicht der 
Wunderwuzzi ist, als der es so gerne angepriesen wird. Drum bin ich da 
ein bischen 'vorbelastet'.

Wundert mich aber eigentlich. Das Serial Objekt hat in VB6.0 meinere 
Erinnerung nach eigentlich recht gut funktioniert.

> Bis .Net 4 warf der Winzigweich-Müll in diesem Fall eine
> "uncatchable" Exception, d.h.: die Anwendung stürzte unrettbar ab
> und es gab kein Mittel, um das zu verhindern.

Das erinnert mich irgendwie an die Fehlermeldung, die im seligen DOS vom 
Treiber direkt ausgegeben wurde, wenn man eine Diskette während eines 
Zugriffs aus dem Laufwerk rausnahm. Das war besonders bei 
Graphikprogrammen recht 'interessant', wenn dann irgendwo am Monitor ein 
paar Pixelhaufen auftauchten.

Die lernens einfach nicht.

> MS muß sich wohl derzeit auf so grundlegende Probleme wie die
> Entwicklung von "gekachelten" Benutzeroberflächen konzentrieren. Da
> bleibt für Randprobleme wie funktionierenden Basiscode wohl echt wenig
> Zeit...

Da sind wir einer Meinung.
D.h. fast. Ich war ja viele Jahre lang der Meinung, das MS nach dem 
Motto vorgeht: Was Excel nicht braucht, braucht auch sonst keiner.

: Bearbeitet durch User
von Jim M. (turboj)


Lesenswert?

Andreas L. schrieb:
> Hmm.. habt ihr eine Idee wie ich das Problem lösen könnte?

Gar nicht. Man stellt das Projekt auf WinUSB um, dabei wird man die 
ganzen Probleme mit den COM Ports auf einmal los. Diese Krücke hat in 
moderner Software nichts mehr zu suchen.

Der WinUSB Treiber kann ab Windoof 8.1 automagsich installiert werden, 
wenn das USB Gerät korrekt programmiert wurde. Siehe auch:
https://github.com/pbatard/libwdi/wiki/WCID-Devices

von Rufus Τ. F. (rufus) Benutzerseite


Lesenswert?

Und mit WinUSB kommunizierst Du mit Geräten, die z.B. eine 
USB-Seriell-Bridge von FTDI, SiLabs oder Prolific haben?

von Borislav B. (boris_b)


Lesenswert?

Wie wäre es denn, wenn du vor dem Schließen der Form erst mal die Timer 
stoppst, ggf. Threads beendest und deinen Port ordentlich schließt?

von Peter II (Gast)


Lesenswert?

Jim M. schrieb:
> Gar nicht. Man stellt das Projekt auf WinUSB um, dabei wird man die
> ganzen Probleme mit den COM Ports auf einmal los. Diese Krücke hat in
> moderner Software nichts mehr zu suchen.

und warum nicht? So ziemlich jede andere Anwendung bekommst es ja auch 
hin, also wird das Problem wohl nicht grundsätzlicher Natur sein.

Die COM port haben eine definierte API mit Timeout Events uä. Das biete 
WinUSB nicht an, das muss man dann alles selber passend zur Hardware 
implementieren.

von Peter II (Gast)


Lesenswert?

Boris P. schrieb:
> Wie wäre es denn, wenn du vor dem Schließen der Form erst mal die Timer
> stoppst, ggf. Threads beendest und deinen Port ordentlich schließt?

ich glaube mich zu erinnern, das eine C# Anwendung sich nicht beendet 
wenn noch ein Thread aktiv ist. Kann also sehr gut sein.

von Andreas L. (andreas_l441)


Lesenswert?

Ich habe schon versucht den Port zu schließen bevor er beendet.
Jedoch sieht es so aus, als würde er immer noch den DataReceived 
Eventhandler aufrufen, da immernoch Daten eintreffen.
Wie verhindere ich das?

von Andreas L. (andreas_l441)


Lesenswert?

Habe es jetzt gelöst:
Das "einfrieren" der Anwendung konnte ich verhindern, indem ich Tipp 3 
von hier befolgt habe:

http://blogs.msdn.com/b/bclteam/archive/2006/10/10/top-5-serialport-tips-_5b00_kim-hamilton_5d00_.aspx

Danach kamen trotzdem noch zwei weitere Exceptions, die ich dann 
abgefangen habe:
1
try
2
                {
3
                    RecievedLine = arduino.ReadLine();
4
                }
5
                catch (System.IO.IOException)
6
                {
7
                    return;
8
                }
9
                catch (System.InvalidOperationException)
10
                {
11
                    return;
12
                }

von bluppdidupp (Gast)


Lesenswert?

ReadLine in DataReceived ist schonmal falsch, wenn dann ReadExisting 
dort nutzen.
Wenn DataReceived ausgelöst wird, aber noch gar keine vollständige Zeile 
angekommen ist, wird ReadLine im Event-Handler hängen bleiben.

Ich würde einfach einen BackgroundWorker nutzen, dort den Port öffnen 
und dort normal ReadLine() nutzen und auf DataReceived ganz verzichten.
BackgroundWorker sind als Hintergrund-Threads markiert und werden 
automatisch gekillt:
[code]Background threads are identical to foreground threads with one 
exception: a background thread does not keep the managed execution 
environment running. Once all foreground threads have been stopped in a 
managed process (where the .exe file is a managed assembly), the system 
stops all background threads and shuts down.[/quote]
https://msdn.microsoft.com/en-us/library/h339syd0(v=vs.110).aspx

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.