Bearbeiten von Steuerelementen aus Threads
Frank Dzaebel, erstellt am: 19.11.2005, zuletzt geändert: 12.06.2010
Kategorie:Multithreading, .NET-Version:1.1-4.0 

Methoden, die sich auf Steuerelemente auswirken, sollten nur aus dem Thread ausgeführt werden, auf dem das Steuerelement erstellt wurde. Das .NET Framework stellt Methoden bereit, die von jedem beliebigen Thread aus ohne Sicherheitsrisiko aufgerufen werden können und die ihrerseits Methoden aufrufen, die mit Steuerelementen anderer Threads interagieren. Die Control.Invoke-Methode (bzw. Dispatcher.Invoke siehe WPF unter .NET 3.x) ermöglicht die synchrone Ausführung von Methoden für Steuerelemente. Ein paar zentrale Info-Links zum Thema und Kurz-Beispielen zu [anonymes Delegate, benanntes Delegate, BackgroundWorker und WPF].

MSDNMAG 07 , WPF-Threads: Erstellen reaktionsfähigerer Anwendungen mit dem Dispatcher -- MSDN Magazine
MSDN 2007 , Webcast: Advanced WPF (Teil 5-6) - Mulithreading
MSDN 2005 , Bearbeiten von Steuerelementen aus Threads
MSDN 2005 , Give Your .NET-based Application a Fast and Responsive UI with Multiple Threads
J. Albahari '08 , Threading in C# - Free E-book [PDF]
22.06.2004 , Sicheres und einfaches Multithreading in Windows Forms, (Part1)  / Part 2   /  Part 3
21.06.2004 , Ein zweiter Blick auf Windows Forms-Multithreading
MSDN 2005 , Control.InvokeRequired-Eigenschaft
MSDN 2006 , Gewusst wie: Threadsicheres Aufrufen von Windows Forms-Steuerelementen
10.10.2005 , Was jeder Entwickler über Multithread-Anwendungen wissen muss
29.10.2005 , Auswirkungen von Low-Lock-Techniken in Multithread-Anwendungen
18.01.2006 , C# Tipps, Teil 1 - Threads, Prozesse und Synchronisierung
J. Rogers '04 , WinForms UI Thread Invokes: An In-Depth Review of Invoke/BeginInvoke/InvokeRequired
25.09.2007 , BackgroundWorker Threads and Supporting Cancel
Synchronisierung
12.4.2010 , Threading (C# und Visual Basic)      /      Threadsynchronisierung (C# und Visual Basic)
1.11.2003 , Synchronisieren des Zugriffs auf eine freigegebene Ressource ...
1.04.2006 , Avoiding And Detecting Deadlocks In .NET Apps with C# and C++
06.2009 / 04.2010 , CHESS - Microsoft Research - Detect "data races", "dead locks", etc.  /  Disciplined Concurrency Testing

1) Kurzlösung: Mit Invoke über anonymes Delegate: 

Wenn Sie beispielsweise in einer Quellcodezeile:
     txtInfo.Text = "meine Info";
folgende Fehlermeldung:  "Ungültiger threadübergreifender Vorgang" (Illegal Cross Thread Operation) bekommen, ersetzen Sie die Zeile (zum Beispiel) einmal mit:
     txtInfo.Invoke(new Action<string>(s=>{txtInfo.Text = s;}), "meine Info");


2) Mit Invoke, über benanntes delegate 


public partial class Form1 : Form
{
  private TextBox textBox1;

  public Form1()
  {
    InitializeComponent();
    this.Load += new System.EventHandler(this.Form1_Load);
  }

  private void Form1_Load(object sender, EventArgs e)
  {
    textBox1 = new TextBox(); textBox1.Width = 150; Controls.Add(textBox1);
    new Thread(new ThreadStart(ThreadMethode)).Start();
  }
  private int counter = 0;
  public void ThreadMethode()
  {
    while (counter < 500)
    {
      textBox1.Invoke(new UpdateTextCallback(this.UpdateText),
          "aus Nicht-UI-Thread:" + (counter++).ToString());
      Thread.Sleep(5);
    }
  }
  public delegate void UpdateTextCallback(string text);
  private void UpdateText(string text)
  {
    textBox1.Text = text;
  }
}

3) Ohne Invoke, mit BackgroundWorker
private TextBox textBox1;int counter = 0;
BackgroundWorker bw;

private void Form1_Load(object sender,EventArgs e)
{ textBox1 =new TextBox(); textBox1.Width = 150; 
  Controls.Add(textBox1); bw =new BackgroundWorker();
  bw.WorkerReportsProgress =true;
  bw.DoWork +=new DoWorkEventHandler(bw_DoWork); bw.RunWorkerAsync();
  bw.ProgressChanged +=new ProgressChangedEventHandler(bw_ProgressChanged);
}
void bw_ProgressChanged(object sender,ProgressChangedEventArgs e) { textBox1.Text ="aus UI-Thread:" + (counter).ToString(); }
void bw_DoWork(object sender,DoWorkEventArgs e) {
while (counter < 500) { bw.ReportProgress(counter++ / 500);
Thread.Sleep(5);//Nicht notwendig, nur künstliche Verlangsamung }
}

4) WPF: Mit Dispatcher.Invoke und delegate in WPF    [Infos]
Wenn Sie Timer nutzen wollen, tuen Sie dies am einfachsten über den DispatcherTimer, denn der ist automatisch immer im "richtigen" Thread (in dem das DispatcherTimer-Objekt erzeugt wurde). Andere Timer würden ggf. einen Thread aus dem Threadpool benutzen, der dann ggf. erst über Invoke auf den richtigen Thread umgeleitet werden müsste. Beachten Sie, daß Invoke wartet, wenn der Erstellungs-Thread in irgendeiner Weise blockiert ist. Eine Lösung kann BeginInvoke sein. Die BackgroundWorker Komponente kann mit WPF weiterhin problemlos eingesetzt werden. Weitere Stichwörter sind DispatcherObject, Freezable, IsFrozen, etc..

Window1.xaml:
<Window x:Class="WpfApplication1.Window1"
   xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
   Title ="Window1" Height="300" Width="300" Loaded="Window_Loaded">
  <Canvas Name="cvs">
    <TextBox Name="textBox1">Anfangs-Text</TextBox>
  </Canvas>
</Window>
Window1.xaml.cs:

private void Window_Loaded(object sender, RoutedEventArgs e)
{
  ThreadStart start = delegate()
  {
    Dispatcher.Invoke(DispatcherPriority.Normal,
      new Action<string>(SetzeStatus), "aus anderem Thread");
  };
  new Thread(start).Start();
}

void SetzeStatus(string meldung)
{
  textBox1.Text = meldung;
}