Autor Beitrag
Rolo
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Mo 02.07.07 11:34 
Hallo zusammen!

Ich bin neu in C# unterwegs und benötige Unterstützung.
Problem:
Aus einem aufrufenden Thread wird ein zweiter gestartet. Dieser soll beim Schließen der Form zuerst geschlossen werden. Dabei bleibt die Anwendung stehen.

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

namespace thread_test
{
    public partial class Form1 : Form
    {
        //delegate
        private volatile bool _shouldStop;
        private delegate void AddReceive(string msg);
        
        private Thread secondThread;

        public Form1()
        {
            InitializeComponent();

            secondThread = new Thread(new ThreadStart(Second));
            secondThread.Name = "Second thread";
            // start thread "Work thread"
            secondThread.Start();
        }

        public void Second()
        {
            int j = 0, i = 0;
            String msg;

            while (!_shouldStop)
            {
                msg = Thread.CurrentThread.Name + ": working..." + j++;
                this.Invoke(new AddReceive(_AddReceive), new object[] { msg });

                Thread.Sleep(1);
            }

            // after close button was pressed
            while (i < 3)
            {
                msg = Thread.CurrentThread.Name + ": the last..." + i++;
                this.Invoke(new AddReceive(_AddReceive), new object[] { msg });
            }
        }
        
        // happens before Form1 is closed
        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            _shouldStop = true;
            // Second thread should do the rest of its task
            secondThread.Join();
        }

        // put msg on richtextbox
        private void _AddReceive(string msg)
        {
            richTextBox1.AppendText(msg + "\n");
            richTextBox1.ScrollToCaret();
        }

        // close button was pressed
        private void button1_Click(object sender, EventArgs e)
        {
            Form1.ActiveForm.Close();
        }
    }
}

Hat jemand eine Idee?

Gruß

Rolo

Moderiert von user profile iconChristian S.: C#-Tags hinzugefügt
Einloggen, um Attachments anzusehen!
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Mo 02.07.07 13:21 
Threading gehört nicht gerade zu meinem Fachgebiet, aber du könntest den Thread an seinem Ende ein Event feuern lassen und der Form verbieten, vor diesem Event zu terminieren.

[edit]Da du Thread und Oberfläche sowieso nicht trennst, könntest du statt dem Event natürlich auch gleich eine Methode der Form aufrufen. [/edit]
Rolo Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Mo 02.07.07 14:36 
user profile iconKhabarakh hat folgendes geschrieben:
Threading gehört nicht gerade zu meinem Fachgebiet, aber du könntest den Thread an seinem Ende ein Event feuern lassen und der Form verbieten, vor diesem Event zu terminieren.

[edit]Da du Thread und Oberfläche sowieso nicht trennst, könntest du statt dem Event natürlich auch gleich eine Methode der Form aufrufen. [/edit]


Die Form terminiert überhaupt nicht.
Wenn ich in der zweiten while-Schleife
"this.Invoke(new AddReceive(_AddReceive), new object[] { msg });"
durch
"Console.WriteLine(msg);"
ersetzte, terminiert die Form manchmal, manchmal auch nicht.
Lasse ich
"secondThread.Join();"
weg, das dafür sorgen sollte, daß der "Second thread" erst abgearbeitet, terminiert und dann die Form geschlossen wird, gibt es nach Rüchkehr aus dem Debugger die Meldungen
"Auf das verworfene Objekt kann nicht zugegriffen werden."
und
"System.Threading.ThreadAbortException".

Thread und Oberfläche sind doch getrennt??? Welche Methode sollte ausgeführt werden? Eine noch zu schreibende???

Gruß

Rolo
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Mo 02.07.07 16:31 
Bis jetzt bin ich davon ausgegangen, dass du auf den Thread warten willst, um alle Ausgaben zu sehen. Geht es allerdings nur um die Exception, würde ich Folgendes vorschlagen:
Du fügst den Thread-Code in eine eigene Klasse ein und spendierst dieser ein public event Action<string> Logging; An dieses hängst du in der Form einen Eventhandler, der den "this.Invoke..."-Aufruf, der sich zuvor im Thread befunden hat, enthält. Beim Schließen des Forms entfernst du diesen Eventhandler wieder, womit dein Thread völlig losgelöst ist und keinen Schaden mehr anrichten kann.
Das wäre dann übrigens auch eine eindeutige Trennung von Daten und Anzeige: der Thread weiß nix vom Form.
Rolo Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Mo 02.07.07 16:57 
user profile iconKhabarakh hat folgendes geschrieben:
Bis jetzt bin ich davon ausgegangen, dass du auf den Thread warten willst, um alle Ausgaben zu sehen. Geht es allerdings nur um die Exception, würde ich Folgendes vorschlagen:
Du fügst den Thread-Code in eine eigene Klasse ein und spendierst dieser ein public event Action<string> Logging; An dieses hängst du in der Form einen Eventhandler, der den "this.Invoke..."-Aufruf, der sich zuvor im Thread befunden hat, enthält. Beim Schließen des Forms entfernst du diesen Eventhandler wieder, womit dein Thread völlig losgelöst ist und keinen Schaden mehr anrichten kann.
Das wäre dann übrigens auch eine eindeutige Trennung von Daten und Anzeige: der Thread weiß nix vom Form.



Da hab' ich mich wohl falsch ausgedrückt. Ich will schon alle Ausgaben aus dem "Second thread" sehen. Nur wennn der "this.Invoke"-Aufruf der zweiten while Schleife angezogen wird (manchmal auch der ersten, hängt wohl davon ab, wo die Abarbeitung der ersten while-Schleife gerade ist, wenn man den Close-Button drückt), dann geht nichts mehr, die Form bleibt stehen und akzeptiert keinerlei Eingaben mehr.
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Mo 02.07.07 18:37 
Dann eben doch meine erste Idee: Füge ein Bool-Feld "running" ein, das du am Anfang des Threads auf true setzt. Am Ende setzt du es wieder auf false und invokest dann Close() des Forms. Im Closing-Eventhandler lässt du die Form nur dann terminieren, wenn running false ist (Thread.Join() natürlich entfernen).
Rolo Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Di 03.07.07 09:17 
user profile iconKhabarakh hat folgendes geschrieben:
Dann eben doch meine erste Idee: Füge ein Bool-Feld "running" ein, das du am Anfang des Threads auf true setzt. Am Ende setzt du es wieder auf false und invokest dann Close() des Forms. Im Closing-Eventhandler lässt du die Form nur dann terminieren, wenn running false ist (Thread.Join() natürlich entfernen).


Das tut nicht, denn wenn er
"this.Invoke(new AddReceive(_AddReceive), new object[] { msg });"
in der zweiten while-Schleife ausführt, hängt er sich schon auf.
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Di 03.07.07 17:48 
"Hängt sich auf"? Meinst du die ObjectDisposedException vom ersten Versuch? Genau deswegen sollst du doch das Bool-Flag erst auf true setzen, wenn beide Schleifen durchlaufen sind und davor jeglichen Dispose-Versuch der Form stoppen, indem du im FormClosing-Eventhandler Cancel auf true setzt.
Rolo Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Mi 04.07.07 10:53 
user profile iconKhabarakh hat folgendes geschrieben:
"Hängt sich auf"? Meinst du die ObjectDisposedException vom ersten Versuch? Genau deswegen sollst du doch das Bool-Flag erst auf true setzen, wenn beide Schleifen durchlaufen sind und davor jeglichen Dispose-Versuch der Form stoppen, indem du im FormClosing-Eventhandler Cancel auf true setzt.


Das Problem ist, daß sich das Form aufhängt, sobald in der zweiten Schleife
"this.Invoke(new AddReceive(_AddReceive), new object[] { msg });"
aufgerufen wird, d. h. eine MessageBox-Ausgabe davor kommt noch, danach nicht mehr. Das Form akzeptiert keinerlei Eingaben mehr. Kann es sein, daß das Invoke auf den GUI-Thread wartet und der ja durch das Join blockiert ist und somit jeder Thread auf den anderen wartet???
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Mi 04.07.07 17:40 
Wir diskutieren hier aneinander vorbei, denn du hast meine Idee anscheinend gar nicht umgesetzt - schließlich gibt es in dieser überhaupt kein Thread.Join(), wie ich geschrieben habe.
Dann eben hier einmal eine Umsetzung, allerdings etwas kryptisiert :zwinker: .
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:
namespace ThreadNonBlockingJoin
{
    delegate void NullAction();

    public partial class Form1 : Form
    {
        volatile bool running = true;

        public Form1()
        {
            InitializeComponent();
        }

        void Log(string s)
        {
            textBox1.AppendText(s + Environment.NewLine);
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = running;
        }

        private void Form1_Load(object sender, EventArgs e)
        {
            ((NullAction)delegate
          {
              for (int i = 0; i < 10; i++)
              {
                  Invoke((Action<string>)Log, i.ToString());
                  Thread.Sleep(1000);
                  if (!running)
                      break;
              }
              running = false;
          }).BeginInvoke(delegate
          {
              Invoke((NullAction)Close);
          }, null);
        }
    }
}
Rolo Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Do 05.07.07 09:10 
[quote="user profile iconKhabarakh"]Wir diskutieren hier aneinander vorbei, denn du hast meine Idee anscheinend gar nicht umgesetzt - schließlich gibt es in dieser überhaupt kein Thread.Join(), wie ich geschrieben habe.

Sorry! Das "kryptisierte" unter "Form1_Load" verstehe ich leider nicht mehr.
Kha
ontopic starontopic starontopic starontopic starontopic starontopic starontopic starhalf ontopic star
Beiträge: 3803
Erhaltene Danke: 176

Arch Linux
Python, C, C++ (vim)
BeitragVerfasst: Do 05.07.07 18:06 
Du kannst einen Thread nicht nur über die gleichnamige Klasse, sondern auch über jeden Delegate durch die Methode BeginInvoke erstellen. Das ganze habe ich dann noch per anonymous methods doppeltgemoppelt gekoppelt ^^ . Aber wie gesagt, Threading ist nicht mein Fachgebiet, also habe ich gleich mal vergessen, den Thread auch wieder ordnungsgemäß zu schließen :angel: . Hier die Korrektur, die zusätzlich etwas verständlicher sein sollte.
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:
    delegate void NullAction();

    public partial class Form1 : Form
    {
        volatile bool running;

        public Form1()
        {
            InitializeComponent();
        }

        void Log(string s)
        {
            textBox1.AppendText(s + Environment.NewLine);
        }

        void ParallelizeMe()
        {
            running = true;
            try
            {
                for (int i = 0; i < 10; i++)
                {
                    Invoke((Action<string>)Log, i.ToString());
                    Thread.Sleep(1000);
                    if (!running)
                        break;
                }
            }
            finally
            {
                running = false;
            }
        }

        void MainformClosing(object sender, FormClosingEventArgs e)
        {
            e.Cancel = running;
        }

        void Form1_Load(object sender, EventArgs e)
        {
            NullAction threadMethod = ParallelizeMe;

            threadMethod.BeginInvoke(delegate(IAsyncResult result)
          {
              // wird bei Threadbeendigung ausgeführt
              threadMethod.EndInvoke(result); // Aufräumen...
              Invoke((NullAction)Close); // ...und Formular schließen
              // (Achtung, wir befinden uns immer noch im zweiten Thread!
              //  Interaktion mit SWF-Controls also nur per Invoke)
          }, null);
        }
    }
}
Rolo Threadstarter
Hält's aus hier
Beiträge: 7



BeitragVerfasst: Fr 06.07.07 08:40 
[quote="user profile iconKhabarakh"]Du kannst einen Thread nicht nur über die gleichnamige Klasse, sondern auch über jeden Delegate durch die Methode BeginInvoke erstellen. Das ganze habe ich dann noch per anonymous methods doppeltgemoppelt gekoppelt ^^ . Aber wie gesagt, Threading ist nicht mein Fachgebiet, also habe ich gleich mal vergessen, den Thread auch wieder ordnungsgemäß zu schließen :angel: . Hier die Korrektur, die zusätzlich etwas verständlicher sein sollte.

Danke für Deine Hilfe!
Ich muß das Ganze ersteinmal richtig verdauen.