none
Parallele Programmabarbeitung und Forms RRS feed

  • Frage

  • Hallo zusammen,

    ich kämpfe schon seit Stunden mit einem Problem und mir fällt einfache keine Lösung ein, ich bin mir sicher ihr könnt mir dabei helfen.

    Ich schreibe zurzeit ein Programm, dass gleichzeitig Input von einem Joy-Stick, Daten über ein Netzwerk schickt und einen Live-Stream von einer Videokamera wiedergibt. Das ganze soll so eine Art "Über-das-Netzwerksteuerung-für-eine-Netzwerkkamera" werden.

    Ich habe jetzt aber schon am Anfang ein Problem mit dem allgemeinen Programmaufbau. Und zwar soll das ganze parallel ablaufen und ich habe das Problem, dass ich gerne Tasks verwenden möchte (da sollten keine Probleme entstehen, weil sie relativ unabhängig arbeiten werden.) Mein Problem ist nun, dass ein Task laut C#-Buch nur statische Methoden parallel ausführen lassen kann. Da das Form aber ein Objekt ist, habe ich Probleme auf die GUI-Elemete zuzugreifen, weil die Methoden ja statisch sind.

    Habts ihr da eine Idee wie man das mit Tasks doch realisieren kann? Ich hab schon versucht mit Events iregendwie eine Lösung zu finden, aber die arbeiten anscheinend nicht parallel und sind somit nutzlos für mich.

     

    Lg,

    Markus L.

    Donnerstag, 11. November 2010 15:03

Antworten

Alle Antworten

  • Mein Problem ist nun, dass ein Task laut C#-Buch nur statische Methoden parallel ausführen lassen kann. Da das Form aber ein Objekt ist, habe ich Probleme auf die GUI-Elemete zuzugreifen, weil die Methoden ja statisch sind.

    Wer sagt denn sowas? Der Task will ein Delegate. fertig. Delegates können sowohl statische methoden, als auch nichtstatische methode sein. Delegates können auch kleine anonmye Methoden sein, also auch ein lambda-Ausdruck.

    Du wirst allerdings noch ein anderes Problem kriegen: Üblicherweise darf auf GUI-Elemente nur der Hauptthread zugreifen. D.h. da kommt ggf. ein Synchronisierungsproblem auf Dich zu, eben weil Dir eigentlich gar nicht erlaubt ist, auf die GUI-Elemente zuzugreifen...

    Donnerstag, 11. November 2010 15:50
  • Danke für die schnelle Antwort. Ich werd das am besten nochmal mit den statischen Methoden und Delegates probieren.

    Das mit der GUI und dem Hauptthread hab ich mir auch schon überlegt. Da werde ich einfach eine Klasse schreiben, die alle für das Programm wichtigen Variablen (z.B.: IPs, Username, Passwort, ...) speichert. Wenn eine Änderung in der GUI vorgenommen wird, wird die Eingabe einfach geprüft und in diese Klasse gespeichert werden. Das sollte eigentlich mit Events funktionieren (Wenn was geändert wird: Event --> Prüfen --> speichern). Ich hoffe das funktioniert.

     

    Lg,

    Markus

    Donnerstag, 11. November 2010 20:38
  • public delegate void myDelegate(String info);

        public partial class Form1 : Form
        {
            Task Engine = new Task(EngineControl);
            Task Video = new Task(VideoStream);
            Task Cam = new Task(CamControl);

            public Form1()
            {
                InitializeComponent();
                this.CenterToScreen();
                //Task beginnt zu arbeiten
                Engine.Start();
            }

            public static void EngineControl()
            {
                //EngineControl-Code here
                myDelegate EngineInfo = new myDelegate(Seilkamera.EngineInfoFunction);  //FEHLER: EngineInfoFunction ist angeblich nicht im Namespace
                //Ausführen des Events
                EngineInfo("TestFehler");
            }

            public static void VideoStream()
            {
                //VideoStream-Code here
            }

            public static void CamControl()
            {
                //CamControl-Code here
            }

            //EVENTS
            private void EngineInfoFunction()
            {
                //Event-Code
            }
        }

    Ich hab mal ausprobiert, dass die Tasks ein Event auslösen sollen, wenn sie eine Nachricht in der GUI anzeigen wollen. Aber anscheinend finder der Compilder die Funktion EngineInfoFunction nicht. Ich weiß nicht wieso, weil die Funktion eindeutig im gleichen Namespace ist. Hat vielleicht jemand eine Lösung?

     

     

    Freitag, 12. November 2010 09:17
  • EngineInfoFunction hat nicht die Übergabeparameter, die von "new myDelegate" erwartet werden. "private void EngineInfoFunction(string s)" wäre besser
    Freitag, 12. November 2010 09:21
  • public static void EngineControl()
            {
                //EngineControl-Code here
                myDelegate EngineInfo = new myDelegate(EngineInfoFunction("TestString"));  //Fehler: Objektverweis erforderlich ("TestString" ist doch ein Objekt?!?!?)
                //Ausführen des Events
                EngineInfo("TestFehler");
            }
     public static void EngineControl()
            {
                //EngineControl-Code here
                myDelegate EngineInfo = new myDelegate(EngineInfoFunction());  //Fehler: Keine Überladungen mit 0 Elementen vohanden
                //Ausführen des Events
                EngineInfo("TestFehler");
            }


    Das hab ich schon vorher probiert. Aber entweder ich brauche einen Objektverweis (welchen der braucht weiß ich aber nicht) oder es existieren keine Überladungen mit 0 Elementen (was ich verstehe aber). Die Daten von der GUI in den Thread einzuschleusen ist relativ leicht (mit der Extra Variablen-Klasse), mein Problem ist von einem Thread aus auf die GUI Elemente zuzugreifen und diese zu verändern.

     

    Freitag, 12. November 2010 10:03
  •   public delegate void myDelegate(String info);
    
      public static void EngineControl()
      {
       //EngineControl-Code here
       myDelegate EngineInfo = new myDelegate(Seilkamera.EngineInfoFunction);
       //Ausführen des Events
       EngineInfo("TestFehler");
      }
      private void EngineInfoFunction(string s)
      {
       //Event-Code
      }
      public static Form1 Seilkamera; //Das wird ja irgendwo so sein?
    
    Das kompiliert bei mir.
    Freitag, 12. November 2010 11:34
  • Das habe ich schon versucht, meine Klassen sind:

    - Program.cs (main - startet das Form)

    - Variables.cs (hat jetzt noch keine Funktion)

    - Form1.cs --> dort steht folgender Code

     

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading.Tasks;
    
    namespace Seilkamera
    {
    
      public delegate void myDelegate(String info);
    
      public partial class Form1 : Form
      {
        Task Engine = new Task(EngineControl);
        Task Video = new Task(VideoStream);
        Task Cam = new Task(CamControl);
    
        public Form1()
        {
          InitializeComponent();
          this.CenterToScreen();
          //Task beginnt zu arbeiten
          Engine.Start();
        }
    
        public static void EngineControl()
        {
          //EngineControl-Code here
          myDelegate EngineInfo = new myDelegate(Seilkamera.EngineInfoFunction);
          //Ausführen des Events
          EngineInfo("TestFehler");
    
        }
    
        public static void VideoStream()
        {
          //VideoStream-Code here
        }
    
        public static void CamControl()
        {
          //CamControl-Code here
        }
    
        //EVENTS
        public void EngineInfoFunction()
        {
          //Event-Code
        }
      }
    }
    
    

    ich bekomme folgende Fehler:

    -Der Typ- oder Namespacename "EngineInfoFunction" ist im Namespace "Seilkamera" nicht vorhanden. (Fehlt ein Assemblyverweis?)
    -"Seilkamera.Form1" enthält keine Definition für "startEngine_Click", und es konnte keine Erweiterungsmethode "startEngine_Click" gefunden werden, die ein erstes Argument vom Typ "Seilkamera.Form1" akzeptiert. (Fehlt eine Using-Direktive oder ein Assemblyverweis?)

     

    Warum will der VS auf einmal ein "startEngine_Click"?

    Freitag, 12. November 2010 11:53
  • Achso! "Seilkamera" ist der namespace! Das kann nicht gehen. Für nicht-statische methoden brauchst du ein object => eine Variable! Diese musst du irgendwo erzeugen. In DEINEM Quellcode ist "Seilkamera" der namespace. In MEINEM Quellcode oben, ist "Seilkamera" eine Variable. (public static Form1 Seilkamera). Wie ist der Name der Variablen, welche das Objekt des Typs "Form1" enthält?

    startEngineClick hast du vermutlich mal aus Versehen beim rumklicken in der Form erzeugt und dann schnell händisch gelöscht. Passiert mir auch laufend. Normalerweise gibts in dem Designercode eine Zeile die du rauslöschen musst, weil der Designer nicht mitgekriegt hat, dass du diese Funktion einfach gelöscht hast. (Also die Datei welche "InitializeComponent" von Form1 enthält. Die Funktion wird im Konstruktor aufgerufen. Form1 besteht aus mehreren Code-Dateien!)

    Freitag, 12. November 2010 12:03
  • using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Windows.Forms;
    
    namespace Seilkamera
    {
      static class Program
      {
        /// <summary>
        /// Entrypoint for the application
        /// </summary>
        [STAThread]
        static void Main()
        {
          Application.EnableVisualStyles();
          Application.SetCompatibleTextRenderingDefault(false);
          Application.Run(new Form1());  
        }
      }
    }
    
    

    Ja bei mir ist Seilkamera der Namespace. Ich hab mich mal von den statischen Tasks gelöst und die statischen Methoden für die Tasks wieder non-static gemacht.

     

    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Windows.Forms;
    using System.Threading.Tasks;
    
    namespace Seilkamera
    {
    
      public delegate void myDelegate(String info);
    
      public partial class Form1 : Form
      {
        Task Engine = null;
        Task Video = null;
        Task Cam = null;
    
        public Form1()
        {
          InitializeComponent();
          this.CenterToScreen();
    
          //Tasks erzeugen
          Engine = new Task(EngineControl);
          Video = new Task(this.VideoStream);
          Cam = new Task(CamControl);
    
          //Task beginnt zu arbeiten
          Engine.Start();
          Video.Start();
          Cam.Start();
        }
    
        public void EngineControl()
        {
          //EngineControl-Code here
          myDelegate EngineInfo = new myDelegate(EngineInfoFunction);
          //Ausführen des Events
          EngineInfo("TestFehler");
        }
    
        public void VideoStream()
        {
          //VideoStream-Code here
        }
    
        public void CamControl()
        {
          //CamControl-Code here
        }
        private void playCam_Click(object sender, EventArgs e)
        {
          Video.Dispose();
        }
      }
    }
    
    

     

    Von Form1 wird bei mir kein Objekt mit Referenz erzeugt, ich habe keine Referenz auf mein Form1-Objekt außerhalb von Form1. In Program.cs wird der Form1 bei mir instanziert.

     

    Freitag, 12. November 2010 13:39
  • Ja bei mir ist Seilkamera der Namespace. Ich hab mich mal von den statischen Tasks gelöst und die statischen Methoden für die Tasks wieder non-static gemacht.
    Also jetzt geht's? Sieht doch gut aus. Und weil "EngineControl" jetzt nicht-statisch ist, hast du ja auch eine object-Referenz bei "new myDelegate(EngineInfoFunction);".... nämlich "this"
    Freitag, 12. November 2010 13:45
  • Ich habe grundsätzliche Frage: Ist es überhaupt möglich von einem Task/Thread aus (der von einem Form erzeugt wurde) möglich auf GUI Elemente (anderer Thread) mit Hilfe von Events zuzugreifen?

     

     

    Samstag, 13. November 2010 08:14
  • Mit "Events" hat das nichts zu tun. Die Frage ist IMMER: Welcher Thread greift auf das GUI-Element zu? Ein Event wird meist von dem Thread ausgeführt, der auch den Event aufruft. Wenn Du trotzdem auf die GUI-Elemente zugreifen willst, musst du das eben mit "Control.Invoke" machen. Oder "Control.BeginInvoke". Jedes Control hat diese Methode. Alternativ kannst du auch einfach GAR KEINE anderen Threads verwenden. Beispielsweise könntest du den System.Windows.Forms.Timer in Deine Form einbauen, der regelmässig einen Event (durch den Hauptthread) aufruft.

    Montag, 15. November 2010 09:58
  • Hallo Markus1991,

    primär ist es bei Windows Forms so, dass nur der Thread der die GUI erzeugt hat, auf diese Zugreifen sollte. Aber natürlich gibt es Möglichkeiten, diesem Thread Aufgaben zu delegieren.

    Das hier sollte dir helfen:

    http://dotnet-snippets.de/dns/control-aus-anderen-threads-aktualisieren-SID830.aspx

    Viele Grüße

    Holger M. Rößler

    • Als Antwort vorgeschlagen Elmar Boye Donnerstag, 18. November 2010 14:06
    • Als Antwort markiert Elmar Boye Donnerstag, 18. November 2010 21:00
    Mittwoch, 17. November 2010 14:57
  • DANKE!!! Es funktioniert entlich und zwar mit Invoke! Ich habe aber ein neues Problem:

    Ich kann einen zuvor beendeten Task nicht noch einmal neu starten. Die Start()-Methode wirft immer eine InvalidOperationException.

    Die C#-Dokumentation sagt folgendes:

    "Der Task ist in keinem gültigen Zustand, um gestartet werden zu können. Es wurde möglicherweise bereits gestartet, ausgeführt oder abgebrochen, oder es wurde möglicherweise auf eine Weise erstellt, die keine direkte Planung unterstützt."

     

    Engine = new Task(EngineControl, EngineToken.Token);
    Engine.Start(); // <--  Exceptionwerfende Methode<br/>
    
    

    Kann ich diesem "ungültigen Zustand" wieder zurücksetzen?

     

     

     

    Donnerstag, 18. November 2010 14:42
  • Hallo,

    ich denke, Du solltest Dich erst einmal mit den Grundlagen beschäftigen,
    denn Dein Code zeigt mir, dass dort einiges fehlen dürfte.
    (Und hier das Problem schon sein könnte, dass wohl nie gültiger Zustand erreicht wird)

    Schau Dir an: Parallele Programmierung in .NET Framework
    Etwas mehr Praxis in: Parallel Programming with Microsoft .NET
    Schau Dir den Beispielcode dazu an und versuche die Konzepte zu verstehen.

    Gruß Elmar

    Donnerstag, 18. November 2010 15:09
  • Hallo,

    Danke, ich werd mich gleich mal ans Lesen machen. Ich weiß, dass mir noch viel fehlt, ich habe erst mit der GUI/Parallelen programmierung in C# begonnen (komme eher aus der PHP-Welt). Der Codeausschnitt ist auch nicht mehr als ein Codeausschnitt. Die einzelnen Programmteile funktionieren schon fast einwandfrei für sich selbst, das einzige Problem ist das Neustarten von Tasks.

    Für das Beenden habe ich 2 Varianten probiert:
    - durch "ThrowIfCancellationRequested()" bzw.
    - durch das Abfragen ob das Token gecancelt wurde (einige Programmteile laufen sowieso in einer Endlosschleife, da habe ich das gleich mitgeprüft.)
    Beide scheinen zu funktionieren.


    Beim Neustart des Tasks habe ich das Problem, dass ich schon probiert habe ein komplett neues Task-Objekt zu erzeugen, da müsse eigentlich jeder Zustand wieder auf Anfang stehen, das ist aber anscheinend nicht so.

    Donnerstag, 18. November 2010 17:59
  • Hallo Markus,

    Dein Code, so weit gezeigt gibt nun wirklich nicht viel her...
    Allerdings haben mich schon die "static" sowie das "MyDelegate"
    die Stirn runzeln lassen, da sie der Erfahrung nach darauf hinweisen,
    das die grundlegenden Konzepte von Klassen und Delegaten (auch Klassen)
    nicht so ganz verstanden wurden.
    Was wiederum eine schlechte Grundlage ist, um sich an die TPL zu machen.

    Arbeite mal die Grundlagen durch.
    Wenn Du dann nicht weiterkommst, poste bitte einige funktionale Ausschnitte,
    die zeigen, was da an Daten bewegt werden soll.

    Das solltest Du in einem neuen Thread tun,
    da dieser doch etwas unübersichtlich geworden ist.

    Gruß Elmar

    P. S.: Ich schliesse diesen mal mit Holgers Antwort ab.

    Donnerstag, 18. November 2010 21:00