Dienstag, 8. April 2008

Dot Net (C#): Invoke()-Methode für MultiThread-Applikationen mit WindowsForms

Um ein Control auf einer WindowsForms von einem anderen Thread aus zu verändern, muss die Invoke()-Methode der Form oder des Controls verwendet werden. Ein Control darf nur von dem Thread geändert werden, von dem es erzeugt wurde!

Folgendes Minimal-Beispiel (unvollständig) zeigt einen Anwendungsfall, in dem ein Thread seinen Status an eine Form berichtet:

Die Klasse MathClass erzeugt Thread-Objekte, deren Methode CalcBackground() in einem Thread ausgefürt wird.Aus dieser Methode heraus soll die GUI über den aktuellen Thread-Status informiert werden:

   1:   
   2:      public class MathClass
   3:      {
   4:   
   5:          private MainForm myForm;
   6:          private int iNumber;
   7:   
   8:          public Thread Initialize(MainForm myform, int number)
   9:          {
  10:              this.myForm = myform;
  11:              this.iNumber = number;
  12:              return new Thread(new ThreadStart(this.CalcBackground));
  13:          }
  14:   
  15:          delegate void UpdateUI(int number, int state);
  16:          delegate void Finish(int number);
  17:   
  18:          public void CalcBackground()
  19:          {
  20:              UpdateUI updateUI = new UpdateUI(this.myForm.updateUIProc);
  21:              Finish finish = new Finish(this.myForm.FinishThread);
  22:   
  23:              try
  24:              {
  25:                  // do something
  26:                  // ...
  27:                  // report status to form:
  28:                  object[] param = new object[2] { this.iNumber, i };
  29:                  myForm.Invoke(updateUI, param); // call myForm.updateUIProc(iNumber, i)
  30:              }
  31:              catch (ThreadAbortException)
  32:              {
  33:                  // clean up
  34:              }
  35:              catch (Exception)
  36:              {
  37:                  // catch any other exception
  38:              }
  39:              finally
  40:              {
  41:                  try
  42:                  {
  43:                      // call myForm.FinishThread(iNumber)
  44:                      myForm.Invoke(finish, new object[] { this.iNumber }); 
  45:                  }
  46:                  catch (ObjectDisposedException e)
  47:                  {
  48:                  }
  49:                  catch (Exception ee)
  50:                  {
  51:                  }
  52:              }
  53:          }
  54:   
  55:      }

Die Mainform erzeugt in der Methode startThreads() zwei neue Threads. Die Methoden updateUIProc() sowie FinishThread() werden per Invoke()-Methode von Thread aus gestartet und laufen selbst im Thread der Mainform:
   1:      public partial class MainForm : Form
   2:      {
   3:   
   4:          public MainForm()
   5:          {
   6:              InitializeComponent();
   7:          }
   8:   
   9:          private void Form1_FormClosing(object sender, FormClosingEventArgs e)
  10:          {
  11:              // kill threads before form closes
  12:              if (anyThreadAlive)
  13:              {
  14:                  killThreads();
  15:              }
  16:          }
  17:   
  18:          public void updateUIProc(int number, int val)
  19:          {
  20:              this.textBox1.AppendText(
  21:                  string.Format("thread {0} has done {1} % of its job", number, val));
  22:          }
  23:   
  24:          public void FinishThread(int number)
  25:          {
  26:              this.textBox1.AppendText(
  27:                  string.Format("thread {0} has finished", number));
  28:          }
  29:   
  30:          private void startThreads()
  31:          {
  32:              Thread th1 = new MathClass().Initialize(this, 0);
  33:              Thread th2 = new MathClass().Initialize(this, 1);
  34:              th1.Start();
  35:              th2.Start();
  36:          }
  37:      }

Es ist nicht erlaubt, das Control Mainform.textBox1 direkt aus dem Thread zu verändern.Ein Control darf nur von dem Thread verändert werden, von dem es erstellt wurde.Um trotzdem ein Control aus einem Thread heraus zu verändern, benutzt man die Methode Invoke() und übergibt dieser ein delegate (Funktionszeiger) sowie ein Parameterarray. Die eigentliche Methode (z.B. updateUIProc() oder FinishThread()) läuft anschließend (in diesem Fall) im Thread der MainForm. Dies hat ebenfalls zur Folge, dass keine der beiden Funktionen parallel mehrmals ausgeführt werden kann.

Keine Kommentare: