C# Threading Problem

godofkills

Lt. Commander
Registriert
Dez. 2009
Beiträge
1.997
Hallo,
in meinem Programm laufen zwei Threads. Nun muss ich, nicht im main Thread, Zugriff auf die Form1 haben. Ich habe ein picturebox array, wo ab und zu ein Bild hinzukommt. Doch so " this.Controls.Add(food[0]); " funktioniert es ja nicht, da ich kein Zugriff auf die Form1 habe.
Ich habe bei google schon gefunden, wie ich es schaffe eine textBox oder eine pictureBox zu updaten. Doch weiß ich nicht, wie ich jetzt jedes mal eine neue picturebox zur form1 hinzufüge.

Ich hoffe ihr könnt mir helfen, oder mir einen anderen Weg zeigen.
 
Variante 1: Du setzt bei dem übergeordneten Control (z.B. TableLayoutPanel) die Eigenschaft CheckForIllegalCrossThreadCalls auf False. Dann lässt das .NET Framework das zwar zu, die Anwendung wird aber wahrscheinlich nicht allzu stabil laufen, wenn ständig in einem Thread Controls hinzugefügt werden und im anderen Thread angezeigt.

Variante 2: Du lässt im Hauptformular einen Timer laufen (z.B. 100ms), der dir dein Formular aktualisiert. Du könntest z.B. einen synchronisierte Queue mit "Queue Controls = Queue.Synchronized(new Queue());" erzeugen und dann von einem Thread Controls einfüllen und im anderen Thread diese in das Form stopfen.
 
Wenn ich das richtig verstanden habe ist dein Problem, dass du aus dem 2. Thread nicht auf die Form zugreifen kannst, weil das verboten ist, richtig?
Wenn ja, jede Control besitzt die Methode Invoke, der du ein Delegate einer Methode übergibst, die in dem Thread ausgeführt wird, von dem die Form erzeugt wurde.
Hier steht mehr dazu: MSDN Library
 
Macht man das in C# nich mit Events und Delegates?

EDIT: Oder was kinglouy gesagt hat :)
 
Kann ich den Thread nicht kurz stoppen. Dann die pb hinzufügen und den thread dann wieder starten?
Ergänzung ()

kinglouy schrieb:
Wenn ich das richtig verstanden habe ist dein Problem, dass du aus dem 2. Thread nicht auf die Form zugreifen kannst, weil das verboten ist, richtig?
Wenn ja, jede Control besitzt die Methode Invoke, der du ein Delegate einer Methode übergibst, die in dem Thread ausgeführt wird, von dem die Form erzeugt wurde.
Hier steht mehr dazu: MSDN Library

Ja das habe ich gerade auf ergooglet, werde mich mal einarbeiten.
 
Kann ich den Thread nicht kurz stoppen. Dann die pb hinzufügen und den thread dann wieder starten?
Kann man auch, nur ich schätze, dass das nicht "die feine Art" ist. Das Stoppen des Formthreads ist weniger das Problem, ihn danach passend neu zu starten schon. Dazu würde ich mit keiner begrenzten Pause arbeiten, sondern mit einer endlosen und den Formthread vom anderen Thread wieder wecken lassen. Dafür müsste man sich aber (soweit wie ich das kann) erst über einen Trick die Möglichkeit beschaffen, den Thread der Form überhaupt von außen aufzrufen, einfach indem man einem Thread-Objekt irgendwann "Thread.Current" zuweist und später von Außerhalb auf diesem Objekt das Wecken aufruft.
 
Ich würd ja nicht den FormThread stoppen, sondern mein eigenen Thread, wo eine Endlosschleife läuft. Und dann kurz die pb hinzufügen.

Ich hab jetzt dieses Tutorial gefunden. Wenn der Thread gestartet wird,wird ja diese Methode aufgerufen: myForm.Invoke(updateUI, param); // call myForm.updateUIProc(iNumber, i) . Meine Frage ist jetzt. Ich habe ja nicht einfach eine picturbox zu aktualisieren sonder muss entweder ein Code Block aus führen oder nur this.controls.add();
Wie ich das verstehe muss ich jetzt die invoke Methode aufrufen. In dem Tut passiert das mit myForm davor. Ist das bei mir jetzt Form1, denn VS 2010 sagt mir das, dass nicht geht.
 
Was das Thread stoppen bringen soll kapier ich nicht ganz.
Imo lässt sich das was du willst am einfachsten/schnellsten per Control.Invoke umsetzen.

Ich hab dir mal ein kleines Beispiel gebastelt.
Der Knackpunkt ist die AddPictureBox Methode. Sie prüft erst, ob der Call aus einem anderen Thread Kontext stammt. Wenn ja, wird die Methode erneut per Control.Invoke im Kontext des MainThread aufgerufen --> Keine CrossThreadException.
PHP:
    public partial class Form1 : Form
    {
        Thread m_Worker;
        delegate void AddPictureBoxInvoker(PictureBox box);

        public Form1()
        {
            InitializeComponent();
        }

        private void AddPictureBox(PictureBox box)
        {
            if (this.InvokeRequired)
            {
                this.Invoke(new AddPictureBoxInvoker(AddPictureBox), box);
            }
            else
            {
                this.flowLayoutPanel1.Controls.Add(box);
            }
        }

        private void Start_Click(object sender, EventArgs e)
        {
            m_Worker = new Thread(delegate()
            {
                string path = @"C:\Users\Stefan\Pictures\Wallpaper";

                foreach (string file in Directory.GetFiles(path))
                {
                    if (Path.GetExtension(file).Equals(".jpg"))
                    {
                        Image img = Bitmap.FromFile(file);
                        PictureBox box = new PictureBox();

                        box.Image = img;
                        box.SizeMode = PictureBoxSizeMode.Zoom;
                        box.Width = 100;
                        box.Height = 100;

                        AddPictureBox(box);

                        //nur damit der Vorteil des Worker Threads besser rüberkommt
                        Thread.Sleep(200);
                    }
                }
            });

            m_Worker.Start();
        }
    }


Schöner ist es imo wenn du das (asynchrone) Laden der Bilder in eine Klasse auslagerst und dem MainThread per Event das Bild übermittelst (in den EventArgs).
Mithilfe der AsyncOperation Klasse kannst du das Event sogar im Kontext des MainThread feuern, ohne dass ein Invoke benötigt wird.
Aber das wäre wohl für deinen Fall etwas overkill.
 
@ Grantig: Anstatt ein delegate zu erstellen und zusätzliche Arbeit dadurch zu haben, würde ich lieber auf so etwas setzen:
Code:
private string InvokeSplitInputFilename
{
  get
  {
    if( TextBoxInputFile.InvokeRequired )
      return (string)TextBoxInputFile.Invoke( new Func<string>( () => InvokeSplitInputFilename ) );
    return TextBoxInputFile.Text;
  }
  set
  {
    if( TextBoxInputFile.InvokeRequired )
      TextBoxInputFile.Invoke( [B]new Action( () => { InvokeSplitInputFilename = value; }[/B] ) );
    else
      TextBoxInputFile.Text = value;
  }
}
 
@Yuuri
Stimmt, so gehts natürlich schneller.
Normalerweise nehm ich auch immer die vorgefertigten Delegates aus dem Framework (+ Lambda Expressions wenn nötig), aber ich wollts bei dem Beispiel extra ausführlich schreiben, weil es dann (hoffentlich) leichter zu verstehen ist.

Das wär dann die kürzere Variante:
Code:
this.Invoke(new Action<PictureBox>(AddPictureBox), box);
 
Nun habe ich mir den Code zusammen gebastelt. Bur hab ich jetzt diese Problem wegen der Erweiterungsmethoden : "Fehler 1 Die Erweiterungsmethode muss in einer nicht generischen statischen Klasse definiert werden"
Und komme da nicht weiter.
 
Zurück
Oben