Autor Beitrag
maggie
Hält's aus hier
Beiträge: 5



BeitragVerfasst: Mi 15.12.10 15:25 
Hi,

ich habe ein seltsames Verhalten meines Programms.

Es kommt immer wieder vor, dass meine Applikation hängen bleibt, wenn ich auf einen Button drücke oder z.B. eine Datei aus per Dialog auswähle. Die Oberfläche friert dann ein. Das Programm arbeitet im Hintergrund aber weiter (Ein Thread befüllt kontinuierlich einen Puffer aus dem Daten über die serielle Schnittstelle geschickt werden).

Wenn ich dann mit der ! rechten Maustaste in der Taskleiste auf die Schaltfläche für dieses Programm klicke, gehts wieder weiter. Kann mir jemand sagen, woran das vielleicht liegen könnte?

Welche Aktion löse ich mit diesem Klick aus?
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 15.12.10 16:02 
Hallo,

synchronisierst du denn den GUI-Thread und den Nebenthread richtig (d.h. bei Zugriff auf Controls Invoke() benutzen)?
Oder verwendest du evtl. Application.DoEvents()? Dieses hat gerade bei Threads einige komische Nebenwirkungen -)
maggie Threadstarter
Hält's aus hier
Beiträge: 5



BeitragVerfasst: Mi 15.12.10 16:16 
Hi,

also bei Buttonklicks verwende ich den vom Designer erzeugten Code:

ausblenden C#-Quelltext
1:
this.btnEEPROMStartRead.Click += new System.EventHandler(this.btnEEPROMStartRead_Click);					


Wie müsste ich einen Buttonklick mittels Invoke denn gestalten?

Grüße Maggie
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 15.12.10 16:38 
Dort ist nicht das Problem, sondern wie schon geschrieben evtl. bei der Thread-Synchronisation.
Wie interagiert denn der GUI-Thread mit dem Nebenthread, d.h. holt er sich die Daten selber aus dem Puffer und zeigt diese an (o.ä.) oder ruft der Nebenthread Controls des GUI-Threads auf (und hier wäre dann Control.Invoke() wichtig)? Nicht daß du evtl. Control.CheckForIllegalCrossThreadCalls gesetzt hast, um evtl. Fehler zu unterbinden?
Dies sollte dann vernünftig programmiert werden, s.a. [FAQ] Controls von Thread aktualisieren lassen (Control.Invoke/Dispatcher.Invoke) (aus einem anderen C#-Forum).

Wenn du selber nicht damit weiterkommst, dann zeigst du am besten mal etwas Code bzgl. des Pufferzugriffs...
maggie Threadstarter
Hält's aus hier
Beiträge: 5



BeitragVerfasst: Mi 15.12.10 16:47 
Hi,

also, wenn ich ein Control in einem anderen Thread modifiziere, mach ich das mit Invoke. Hier einfach mal der Code des Sendethreads:

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:
private void SendPort()
        {
            while (keepSending)
            {
                if (port.IsOpen)
                {
                    byte[][] copy;
                    int anzahl;

                    // Gegenseitiger Auschluss, um Interferenz mit writePort zu vermeiden
                    lock (sendBuffer)
                    {
                        if (sendBuffer.Count >= 10)
                        {
                            anzahl = 10;
                        }
                        else
                        {
                            anzahl = sendBuffer.Count;
                        }
                        copy = new byte[anzahl][];
                        //copy = new byte[sendBuffer.Count][];
                        
                        sendBuffer.GetRange(0, anzahl).CopyTo(copy);
                        sendBuffer.RemoveRange(0, anzahl);
                        //sendBuffer.CopyTo(copy);
                        //sendBuffer.Clear();

                        SetText(tbQueue, sendBuffer.Count.ToString());
                    }

                    foreach (var item in copy)
                    {
                        if (item == null)
                        {
                            break;
                        }

                        try
                        {
                            port.Write(item, 0, Device.MESSAGE_SIZE);
                        }
                        catch (TimeoutException)
                        {
                            SetLabel(lbComStatus, "Status: Senden: Timeout");
                        }
                        catch (Exception e)
                        {
                            AppendText(tbConsole, e.Message);
                        }
                    }

                    Thread.Sleep(100);
                }
                else
                {
                    Thread.Sleep(1000);
                }               
            }
        }


Der Button, der gedrückt wird:

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:
private void btnEEPROMStartRead_Click(object sender, EventArgs e)
        {
            pbEEPROM.Value = 0;
            eeprom_percentage = 0;
            eeprom_last_timestamp = 0;
            eeprom_current_timestamp = 0;
            EnableButton(btnEEPROMStartRead, false);

            if (csvfile_eeprom != null)
            {
                if (!logToFile_eeprom)
                {
                    writeStartLog(csvfile_eeprom);
                    logToFile_eeprom = true;
                }

                cbADCLesen.Checked = false;
                eeprom_bytes_read = 0;
                writePort(Device.MSG_EEPROM_READ_EXISTING);
            }
            else
            {
                objResult = objDialog.ShowDialog();
                if (objResult == DialogResult.OK)
                {
                    cbADCLesen.Checked = false;
                    logToFile_eeprom = true;
                    eeprom_bytes_read = 0;
                    setCSVWriter(out csvfile_eeprom, lbEEPROMLogFile);
                    writePort(Device.MSG_EEPROM_READ_EXISTING);
                }
                else
                {
                    EnableButton(btnEEPROMStartRead, true);
                }
            }
        }


Mit writePort wird dann in den Sendepuffer geschrieben:

ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
private void writePort(byte senddata)
        {
            byte[] data = prepareSendData(senddata);

            // Gegenseitiger Auschluss, um Interferenz mit Sendethread zu vermeiden
            lock (sendBuffer)
            {
                if (sendBuffer.Count < 32)
                {
                    sendBuffer.Add(data);
                }
            }
        }
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 15.12.10 18:51 
Hi maggie,

wenn du in SetText und SetLabel Invoke() verwendest, dann sehe ich so auch keinen Fehler.
Auch deine Leseschleife ist in Ordnung (locking und Thread.Sleep).

Evtl. kannst du ja mal einzelne Codeteile auskommentieren (als erstes mal den ganzen Lesethread) und schauen, ob dann das Fenster immer noch hängt...

Gerade wo ich diesen Text schreibe, fällt mir noch auf, daß du auch SetText innerhalb des locks verwendest. Da aber im GUI-Thread beim Button-Click writePort mit dem locking aufgerufen wird, könnte dies der Grund für den Hänger sein.

Also besser so:
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
lock (sendBuffer)
{
  ...
}

SetText(...);

Du brauchst ja nur schreibende Zugriff zu locken.

Auch bei writePort könntest du daher
ausblenden C#-Quelltext
1:
2:
3:
4:
5:
6:
7:
if (sendBuffer.Count < 32)
{
    lock (sendBuffer)
    {
        sendBuffer.Add(data);
    }
}

verwenden, da ein locking immer relativ teuer ist.
Analog dann auch bei SendPort nur das locking um 'sendBuffer.RemoveRange(0, anzahl);' herum.

So ich hoffe, das hilft dir jetzt weiter.
maggie Threadstarter
Hält's aus hier
Beiträge: 5



BeitragVerfasst: Mi 15.12.10 19:23 
Hi,

ich denke du bist auf dem richtigen Weg. Ich habe nämlich beim Rumprobieren auch schon festgestellt, dass das SetText irgendeinen Einfluss darauf hat, konnt mir aber noch keinen genauen Reim drauf machen. Ich werds morgen gleich mal ausprobieren. Danke schon mal für die Hilfe.

Grüße

Maggie
maggie Threadstarter
Hält's aus hier
Beiträge: 5



BeitragVerfasst: Do 16.12.10 08:59 
Hi,

wie schon vermutet, war das das Problem. Vielen Dank schon mal ;)

Aber ich hab noch eine Frage: Wieso führt das zum Hängenbleiben der Applikation?

Grüße

Maggie
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: Do 16.12.10 11:32 
Hallo maggie,

schön, daß ich richtig "geraten" habe und es nun funktioniert -)

Es führte zum Hänger, weil die beiden locks bei "SendPort" und "writePort" sich gegenseitig blockierten, denn ein Invoke() erzeugt ja einen Threadwechsel in den GUI-Thread und da dann aber "writePort" auf die Freigabe des locks wartet, hast du eben ein sog. "gegenseitiges Blockieren". Und das explizite Anklicken des Taskbar-Buttons aktiviert von außen die Message-Loop im GUI-Thread und somit kann dann das Invoke() ausgeführt werden und anschließend auch der lock freigegeben werden... (Abhilfe hätte evtl. auch ein BeginInvoke() gebracht)

Multithreading ist eine komplexe Angelegenheit - evtl. hilft dir der Artikel Multi-Threaded Programmierung noch ein bißchen weiter.

_________________
... denn die Kunst ist nur der Schein anders als der Rest zu sein.