Die Verwendung von Threads Events und Delegates

Um die Windows-App „IR-Dekoder“ zu erstellen waren Voruntersuchungen mit Threads Events und Delegates erforderlich um eine Solide Basis dafür geschaffen. Die erstellte Test App erläutert das Zusammenspiel der Threads, der Benutzeroberfläche und dem nötigen Event – Handling.

Beschreibung der App-Funktionen

Das Projekt enthält 3 Threads und eine Benutzeroberfläche mit 2 Buttons und einem Textfeld für die Ausgaben der Threads.

Mit Button „Send to Thread 1“ wird Thread über den Class Setter gestartet und gibt daraufhin die erste Zeile in Ausgabefenster aus. Thread 1 schickt nun ein Event an Thread 2, der die 2te Zeile ausgibt. Thread 2 schickt darauf ein Event an Thread 3, der Zeile 3 ausgibt. Damit ist die Action von Button 1 beendet.

Button „Send to Thread 2“ aktiviert Thread 2 über den Setter und Thread 2 gibt darauf die 4te Zeile aus und aktiviert Thread 3 über ein Event. Thread 3 gibt darauf die 5te Zeile aus. Damit ist die Aktion von diesem Button beendet.

Die Ausgabe der Threads in der Benutzeroberfläche „Form1“ werden ebenfalls über zeitlich entkoppelte Events (Invoke) durchgeführt

Die einzelnen Details und Zusammenhänge von Threads Events und Delegates werden nun erklärt.

				
					using System;
using System.Threading;
using System.Windows.Forms;

namespace My_Threading
{
    public partial class Form1 : Form
    {
        Thread Th1 = null;
        Thread Th2 = null;
        Thread Th3 = null;

        public Thread1 thread1 = new Thread1();
        public Thread2 thread2 = new Thread2();
        public Thread3 thread3 = new Thread3();

        private int msg = 1;
        public Form1()
        {
            InitializeComponent();
            // Instanziere die Thread Classes

            // Starte die Threads mit der Funktion 'RaiseTheEvent'
            Th2 = new Thread(thread2.RaiseTheEvent2);
            Th2.Start();
            Th1 = new Thread(thread1.RaiseTheEvent1);
            Th1.Start();
            Th3 = new Thread(thread3.RaiseTheEvent3);
            Th3.Start();

            // Set function to delegate
            Thread2.TestEvent += thread1.Subscriber1;
            Thread2.TextEvent += UpdateTextBox; ;
            Thread1.TestEvent += thread2.Subscriber2;
            Thread1.TextEvent += UpdateTextBox;
            Thread3.TextEvent += UpdateTextBox;
            Thread2.Msg_Subscriber_Thread3 += thread3.More_Subscriber_Thread3;
            Console.WriteLine("in the Main thread");
        }

        private void Btn_to_th2(object sender, EventArgs e)
        {
            // Starte Event senden mit Setter "Start" der Class
            thread2.Start = msg++;
        }

        private void Btn_to_th1(object sender, EventArgs e)
        {
            // Starte Event senden mit Setter "Start" der Class
            thread1.Start = msg++;
        }
        private void Form1_Load(object sender, EventArgs e)
        {

        }

        private void Form1_FormClosing(Object sender, FormClosingEventArgs e)
        {
            // Determine if text has changed in the textbox by comparing to original text.
            string module = "Form1_Closing: ";
            Console.WriteLine("{0}", module);
            Th1.Abort();
            Th2.Abort();
        }

        public void Subscriber3(string text)
        {
            string module = "Forms->Subscriber3: ";
            Console.WriteLine("{0}Event message from {1}", module, text);
            
        }

        public void UpdateTextBox(string text)
        {
            if(textBoxForm1.InvokeRequired)
            {
                textBoxForm1.Invoke(new Action(() => textBoxForm1.Text += text + Environment.NewLine));
                Console.WriteLine("Invoke");
            }
            else
            {
                textBoxForm1.Text += text + Environment.NewLine; // Fügt Text mit Zeilenumbruch hinzu
                Console.WriteLine("Invoke + Newline");

            }
        }
    }
}
				
			

Die Benutzeroberfläche "Form1"

Form1 ist die Start-Methode der Applikation  und initialisiert somit alle notwendigen Klassen, Threads und Events.

In Zeile 9-15 werden die Thread Klassen instanziiert. 

Die Zeilen 24-29 Startet die Threads mit der Methode „RaiseThe Event“.

Die sehr wichtigen Zeilen 32-37 verbindet die Events mit den Methode.

Zeile 41-51 bearbeitet die Button Events aus der Benutzeroberfläche.

In Zeile 73-86 befindet sich die  Subscriber Methode für die asynchrone Bearbeitung der Textausgabe. Siehe dazu auch Zeile 33, 35 und 36, welche die Subscriber den Events zuordnet.

Download von Form1

				
					using System;
using System.Threading;

namespace My_Threading
{
    public class Thread1
    {
        private int _start = 0;
        public void Tread1()
        {
            string module = "Thread1->Konstructor: ";
            Console.WriteLine("{0}", module);

        }
        public int Start
        {
            get { return _start; }
            set { _start = value; }
        }

        public delegate void myDelegat(int t);
        public static event myDelegat TestEvent;

        public delegate void textDelegat(string t); 
        public static event textDelegat TextEvent;    

        public void RaiseTheEvent1()
        {
            string module = "Thread1->RaiseTheEvent1: ";
            while (true)
            {
                Thread.Sleep(500);
                if (_start > 0)
                {
                    Console.WriteLine("{0}", module);
                    TestEvent?.Invoke(_start);
                    TextEvent?.Invoke("Text from Thread1 Cmd = " + _start.ToString());
                    _start = 0;
                }
            }

        }

        public void Subscriber1(int cmd)
        {
             _start = cmd;
            string module = "Thread1->Subscriber1: ";
            Console.WriteLine("{0}Event from Th2 was rised with {1}", module, cmd);
        }
    }
}

				
			

Thread 1

Zeilen 9-19 zeigen den Klassen-Konstruktor mit dem Sette/Getter von „Start“.

Die nun wichtigen Zeilen 21-25 setzen die Delegates für diesen Thread.

In zeile 27 findet sich die Methode „RaisTheEvent1“, die als Thread-Start in Form1 angegeben wurde.

Mit Zeile 32 schläft der Thread jeweils 500ms.

Wenn der Button „Send to Thread 1“ gedrückt wird, wird die Klassenvariable „Start“ zu =! 0. Das aktiviert die If-Anweisung in Zeile 33.

Zeile 36 sendet nun einen „TestEvent“ der wiederum in Form1-Zeile 34 mit der Methode „thread2.Subscriber2“ verbunden ist. Dieses sendet den Event an Thread2.Subscriber2.

Zeile 37 sendet nun einen „TextEvent“ der wiederum in Form1-Zeile 35 mit der Methode „Form1.UpdateTextBox“ verbunden ist und den Text zeitlich entkoppelt in der TextBox von Form1 ausgibt.

Ab Zeile 44 befindet sich die Methode „Subscriber1, welche den erhaltenen Event auf die Variable _start  überträgt und damit die If-Anweisung im Thread aktiviert. 

Download von Thread1

				
					using System;
using System.Threading;


namespace My_Threading
{
    public class Thread2
    {
        public int _start =0;
        public void Tread2()
        {
            string module = "Thread2->Konstructor: ";
            Console.WriteLine("{0}", module);

        }
        public int Start
        {
            get { return _cmd; }
            set { _cmd = value; }
        }

        public object name = "Thread2";
        private int _cmd = 0;

        public delegate void textDelegat(string t);
        public static event textDelegat TextEvent;
        public delegate void EventHandler3<MoreEventArgs>(object sender, MoreEventArgs e);
        public static event EventHandler3<MoreEventArgs> Msg_Subscriber_Thread3;



        public void RaiseTheEvent2()
        {
            string module = "Thread2->RaiseTheEvent2: ";
            while (true)
            {
                Thread.Sleep(500);
                if (_cmd > 0)
                {
                    Console.WriteLine("{0}", module);
                    //TestEvent?.Invoke(_cmd);
                    TextEvent?.Invoke("Text from Thread2 Cmd = " + _cmd.ToString());
                    // Using eventhandler with more arguments
                    MoreEventArgs args = new MoreEventArgs
                    {
                        command = _cmd,
                        msg = "Message with MoreEventArgs()"
                    };

                    Msg_Subscriber_Thread3?.Invoke(name, args);
                    _cmd = 0;
               }
             }

        }

        public void Subscriber2(int m)
        {
            _cmd = m;
            string module = "Thread2->Subscriber2: ";
            Console.WriteLine("{0}Event from Th1 was rised with {1}", module, m);
        }
    }
}

				
			

Thread 2

Die Funktionsweise dieses Threads ist identisch zu Thread1, jedoch wird kein Test-Event an Thread1 geschickt, was eine Endlosschleife ergeben würde. Sie wird von Thread1 und dem Button „Send to Thread2“ aktiviert.

In Zeile 44 wird die Klasse „MoreEventArgs“ instanziert, um mehrere Parameter in einem Event zu versenden. Die Deklaration den Klasse „MoreEventArgs“ befindet sich weiter unten.

Zeile 46-47 setzt nun die Werte für den Event.

Zeile 50 startet mit „Msg_Subscriber_Thread3“ den Event, wenn eine Methode in Form1 Zeile 37 zugewiesen wurde.

Ab Zeile 57 befindet sich die Methode „Subscriber2, welche den erhaltenen Event auf die Variable _start  überträgt und damit die If-Anweisung im Thread aktiviert, gleich Funktion wie in Thread1.

Download von Thread2

				
					using System;
using System.Threading;

namespace My_Threading
{
    public class Thread3
    {
        private int _start = 0;
        public void Tread3()
        {
            string module = "Thread1->Konstructor: ";
            Console.WriteLine("{0}", module);

        }
        public int Start
        {
            get { return _start; }
            set { _start = value; }
        }

        private string _msg = "";   
        public delegate void textDelegat(string t); 
        public static event textDelegat TextEvent;

        public void RaiseTheEvent3()
        {
            string module = "Thread3->RaiseTheEvent3: ";

            while (true)
            {
                Thread.Sleep(500);
                if (_start > 0)
                {
                    Console.WriteLine("{0}", module);
                    TextEvent?.Invoke("Text from Thread3 Cmd = " + _start.ToString() + " Msg = " + _msg);
                    _start = 0;
                }
            }

        }

        public void More_Subscriber_Thread3(object name,MoreEventArgs e)
        {
            _start=e.command;
            _msg=e.msg;
            string module = "Thread1->Subscriber_Thread3: ";
            Console.WriteLine("{0}Event from Th2 was rised with {1}", module, _start);
        }
    }
}
				
			

Thread 3

Die Funktionsweise dieses Threads ist identisch zu Thread1 und Thread2,es wird aber kein Test-Event an Thread1 und Thread2 geschickt, was eine Endlosschleife ergeben würde. Der Thread wird nur von Thread2 aktiviert.

Zeile 52 startet mit „Msg_Subscriber_Thread3“ den Event, wenn eine Methode in Form1 Zeile 37 zugewiesen wurde.

Ab Zeile 42 befindet sich die Methode „More_Subscriber_Thead3“, welche den erhaltenen Event auf die Variable „_start“ und „_msg“ überträgt und damit die If-Anweisung im Thread aktiviert, gleich Funktion wie in Thread1.

Die Variable „-msg“ wird in Zeile 35 mit dem „TextEvent“ an „Form1“ gesendet und dort in der TextBox dargestellt.

Download von Thread3

				
					using System;

namespace My_Threading
{
    public class MoreEventArgs : EventArgs {
        // Message arguments for Thread3 communication
        public int command;
        public string msg;
    }
}

				
			

MoreEventArgs

Ab Zeile 5 wird die Klasse „MoreEventArgs“ deklariert. Sie dient dazu mehr als einen Parameter in einem Event zu übertragen.

Download von MoreEventArgs