none
Backgroundworker mit mehreren Variablen aus WPF UI Thread

    Frage

  • Hallo,

    ich habe folgende fiktive Situation:

    Eine WPF-Anwendung, mit mehreren Eingabefeldern.
    Auf Knopfdruck soll Arbeit in einem separaten Thread erledigt werden.
    Dieser benötigt aber die Werte der Eingabefelder.

    Bislang habe ich dies mit Hilfe eines Backgroundworkers wie folgt erledigt:

    CodeBehind:

    public partial class MainWindow {
    	public static readonly DependencyProperty Text1Property = ...
    	public static readonly DependencyProperty Text2Property = ...
    	public static readonly DependencyProperty Text3Property = ...
    	
    	public static readonly DependencyProperty ListeProperty = ...
    	
    	// Hier noch die Properties zu den DepedencyProperties...
    	
    	private BackgroundWorker _worker;
    	
    	
    	public void MainWindow() {
    		InitializeComponent();
    		
    		_worker = new BackgroundWorker();
    		_worker.DoWork += TuWas;
    	}
    	
    	public void Arbeite_Executed(object sender, ExecutedRoutedEventArgs e) {
    		// Wird bei Knopfdruck durch CommandBinding ausgelöst...
    		_worker.RunWorkerAsync();
    	}
    	
    	private void TuWas(object sender, DoWorkEventArgs e) {
    		// Variablen Threadsicher ermitteln
    		string text1;
    		string text2;
    		string text3;
    		List<string> liste;
    		
    		Dispatcher.Invoke(new Action(() => {
    			text1 = Text1;
    			text2 = Text2;
    			text3 = Text3;
    			liste = Liste1;
    		}));
    	
    	
    		// Berechne ganz viele Sachen!	
    		Thread.Sleep(5000);
    		
    	}

    Dieser Ansatz ist sehr unschön und umständlich.
    Ich habe schon an dynamic / ExpandoObject zur Übergabe an den BackgroundWorker gedacht,
    habe aber keine schöne Lösung gefunden.

    Gibt es hier eine "vernünftige" Lösung?

    Dienstag, 2. Juli 2013 09:35

Antworten

  • Hi,

    ich denke die Lösungen sind einfach zu Anwendungsspezifisch.

    Alles Andere wäre auch zu einfach gewesen.

    • Als Antwort markiert Iqscope Dienstag, 2. Juli 2013 13:07
    Dienstag, 2. Juli 2013 13:06

Alle Antworten

  • Hi,
    eine vernünftige Lösung wäre die Bindung der Oberfläche an Eigenschaften, z.B. so:

    <Window x:Class="WpfApplication1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            Title="MainWindow" Height="350" Width="525">
      <StackPanel>
        <TextBox Text="{Binding Text1}" Margin="5" />
        <Button Name="btn" Content="Start" Click="btn_Click" Margin="5" />
        <Label Content="{Binding Ergebnis}" Margin="5" />
      </StackPanel>
    </Window>

    Dazu der ViewModel:

    using System.ComponentModel;
    namespace WpfApplication1
      {
      class ViewModel : INotifyPropertyChanged
        {
        public string Text1 { get; set; }
        public string Ergebnis { get; set; }
        internal void Start()
          {
          (new System.Threading.Thread(new System.Threading.ParameterizedThreadStart(DoWork))).Start();
          }
        void DoWork(object obj)
          {
          var x = Text1;
          System.Threading.Thread.Sleep(5000);
          Ergebnis = string.Format("Ergebnis: {0}", x);
          if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs("Ergebnis"));
          }
        public event PropertyChangedEventHandler PropertyChanged;
        }
      }

    --
    Peter
    • Als Antwort vorgeschlagen Peter FleischerMVP Dienstag, 2. Juli 2013 10:01
    • Nicht als Antwort vorgeschlagen Iqscope Dienstag, 2. Juli 2013 12:11
    Dienstag, 2. Juli 2013 10:01
  • Danke für deine Antwort.

    Aber wurde in deiner Lösung das Problem nicht nur um eine Klasse "verschoben"?

    Können in dieser Lösung nicht auch Zugriffsverletzungen entstehen?
    Hier ist, so wie ich das sehe, keinerlei Threadsicherheit gegeben!?

    Dienstag, 2. Juli 2013 10:20
  • Hi,
    wo siehst Du eine Tread-Unsicherheit? Die UI holt sich die Daten aus den Eigenschaften. Die Eigenschaften sind threadsicher. Die Verarbeitung läuft in einem anderen thread und greift nicht auf die Oberfläche zu, sondern nur auf die threadsicheren Eigenschaften. Lediglich das PropertyChanged-Ereignis triggert das "Refresh" der Oberfläche. Wenn es da Probleme gibt, kann man das Ereignis mit dem UI-Dispatcher auslösen.

    --
    Peter

    Dienstag, 2. Juli 2013 10:39
  • Hm,

    ich muss mir mal ein Beispielprojekt basteln.

    Ich würde z.B. lieber DependencyObject verwenden statt das
    INotifyPropertyChanged manuell zu implementieren...

    Dienstag, 2. Juli 2013 11:04
  • Hi,
    Deine ursprüngliche Frage war, wie eine vernünftige Lösung aussieht. MVVM ist ein vernünftiges Entwurfsmuster. Die Nutzung von DependencyProperties ist für den geschilderten Anwendungsfall aus meiner Sicht keine vernünftige Lösung. Du hast immer das Problem, den threadübergreifenden Zugriff absichern zu müssen, da Du nicht der UI überlässt, was sie zu machen hat (Daten holen), sondern selbst die Daten in die UI schaffen willst.

    --
    Peter

    Dienstag, 2. Juli 2013 11:29
  • Hi,

    ich glaube, dann muss ich meine Frage etwas präzisieren.

    Der Fokus soll hier auf das Arbeiten mit mehreren Threads sein.
    Um genau dieses immer wiederkehrende Problem der Übergabe bzw. Kopie von vielen Variablen in einen separaten Thread zu lösen/vereinfachen. 

    Wie du schon gesagt hast, vielleicht gibt es keine "Lösung" dafür.

    Aber ein guter Ansatz, der vieles vereinfacht, wäre ein Anfang.


    • Bearbeitet Iqscope Dienstag, 2. Juli 2013 11:59 Grammatik
    Dienstag, 2. Juli 2013 11:38
  • Hi,
    auch, wenn das ViewModel-Object in einem anderen thread erzeugt wird, sind deren Eigenschaft treadsicher und es ist ohne Bedeutung. Das kannst Du ganz einfach überprüfen, indem Du das ViewModel-Objekt in einem anderen thread erzeugst.

    Die Zeile var x ... ist auch ohne Bedeutung für die threadsichere Arbeit. Im konkreten Beispiel wollte ich nur zeigen, dass am Ende der langen Verarbeitung der Ausgangswert genutzt wurde. Wenn Du es anders haben willst, dann mache es anders.

    Ich verstehe leider Deine Argumentation nicht.

    --
    Peter

    Dienstag, 2. Juli 2013 12:15
  • Hi,

    meine Frage über die Threadsicherheit hat die Sache durcheinander gebracht. Diese ist in diesem Fall unbegründet. Lassen wir die Threadsicherheit beiseite.

    Mir geht es um die eleganteste Methode, Variablen für andere Threads zur Verfügung zu stellen. Das Beispiel ist nur Mittel zum Zweck. Also die Methode TuWas(), in der die Variablen über den Dispatcher ermittelt werden.

    Dienstag, 2. Juli 2013 12:27
  • Hi,

    Peters Ansatz ist meines Erachtens ein sehr guter. Oberfläche und Logik zu trennen macht meistens Sinn.

    Wenn du auf die gleichen Daten aus mehren Threads zugreifen willst, musst natürlich schauen wie und wann sie Synchronisiert werden. Das hängt aber meistens von der genauen Aufgabenstellung ab.

    Grundlegend gibt es aber schon einige bekannte Lösungsansätze wie z.B. das Consumer / Producer Pattern.

    Schau mal hier:

    MFG

    Björn


     

    Dienstag, 2. Juli 2013 12:52
  • Hi,
    wenn Du in einem thread auf Member in einem anderen thread zugreifen willst, musst Du die Threadsicherheit  beachten. Das kann man nicht so einfach beiseite lassen. Bei threadsicheren Member hast Du keine Probleme außer vielleicht Probleme in Deiner Programmlogik.

    Wenn Du tread-affine Member hast, musst Du den Dispatcher bemühen, damit er Deine Anfrage in den entsprechenden thread einordnet.

    Da aber häufig Member beim Lesen treadsicher sind, nicht aber beim Schreiben, kann man mit Hol-Vorgängen viele Klippen umschiffen.

    --
    Peter

    Dienstag, 2. Juli 2013 12:56
  • Hi,

    ich denke die Lösungen sind einfach zu Anwendungsspezifisch.

    Alles Andere wäre auch zu einfach gewesen.

    • Als Antwort markiert Iqscope Dienstag, 2. Juli 2013 13:07
    Dienstag, 2. Juli 2013 13:06
  • Hallo,

    BackgroundWorker ist eine gute Wahl, wenn Du die Dienste, die diese Komponente anbietet, auch wirklich brauchst. Ich sehe hier keine ästhetischen Bedenken, eine saubere Angelegenheit.

    Wenn Du nicht die Klasse System.Threading.Thread verwenden willst und keine eigene Thread-Verwaltung schreiben willst, bleibt dir eigentlich nur der Weg über den ThreadPool; entweder direkt (ThreadPool.QueueUserWorkItem), oder vermittelt über andere Klassen wie z.B. den BackgroundWorker (oder Task, oder Parallel.For).

    Unabhängig von der verwendeten UI-Technologie, sobald man aus einem non-UI-Thread die Benutzeroberfläche aktualisieren möchte, muss Thread-Synchronisierung betrieben werden. Üblicherweise geschieht das über Invoke() (ob Control.Invoke() oder Dispatcher.Invoke()).

    Deine TuWas()-Methode geht hier eigene Wege, indem sie den Invoke-Mechanismus dazu "mißbraucht", den aktuellen State verschiedener Felder in lokale Variable zu kopieren. Ich hoffe, dir ist klar, dass dabei (mindestens) ein Thread-Switch (ganz unnötigerweise) stattfinden muss.

    Zum einen könntest Du diesen State ganz einfach über RunWorkerAsync(Object) an den Hintergrundthread übergeben, zum anderen sehe ich keine Threading-Probleme beim direkten lesenden Zugriff auf deine DPs (falls Du etwas aktuellere Werte brauchst). Leichte Synchronisierung über ReaderWriterLockSlim sollte hier ausreichen.

    Problematischer beim Threading wird es erst wenn auf Felder schreibend zugegriffen wird. Klassischerweise löst man das Problem über Sperren (lock/Monitor , ReaderWriterLockSlim, MemoryBarrier etc), bei hochperformanten Anwendungen verwendet man hingegen immutable Auflistungen um dem Problem ganz aus dem Weg zu gehen (BlockingCollection und Konsorten verlagern nur das Problem).

    Gruß
    Marcel

    Dienstag, 2. Juli 2013 14:15
    Moderator