none
Zweites Invoke RRS feed

  • Frage

  • Hallo Community!

    Ich habe eine Datenverarbeitung (über async await) in einen Daten-Thread ausgelagert.

    Nun möchte ich in der Datenverarbeitungsroutine den Fortschritt der Datenverarbeitungsschritte nacheinander anzeigen.

    Ich habe jedoch das Problem, dass nur das erste Invoke die Oberfläche aktualisiert.

    Zum besseren Verständnis habe ich ein kleines Demo erstellt.

    private async void Btn_Start_Click(object sender, RoutedEventArgs e)
    {
        TblAnzeige.Text = await System.Threading.Tasks.Task<string>.Run(() => DatenVerarbeitung());      // Datenaktualisierungs-Thread
    }
    
    private string DatenVerarbeitung()
    {
        for (int i = 0; i < 10; i++) // 9 Einzel-Vorgänge (a 2s) zur Datenverarbeitung
        {
            System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() => SyncAnzeige(10, "Daten verarbeiten: " + i.ToString())));         // Daten verarbeiten (Anzeige)
            System.Threading.Thread.Sleep(2000);        // Datenverarbeitung simulieren
            if (i == 2) System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() => SyncAnzeige(2000, "3: 2s warten")));               // Bei 3, 2s warten
            if (i == 5) System.Windows.Application.Current.Dispatcher.Invoke(new System.Action(() => SyncAnzeige(2000, "5: 2s warten")));               // Bei 5, 2s warten
        }
        return "Fertig!";
    }
    
    private void SyncAnzeige(int WarteZeit, string Text)
    {
        System.Threading.Thread.Sleep(WarteZeit);
        TblAnzeige.Text = Text;
        System.Threading.Thread.Sleep(WarteZeit);
    }

    <Window x:Class="Arduino1.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            mc:Ignorable="d"
            Title="MainWindow" Height="150" Width="300" WindowStartupLocation="CenterScreen">
        <StackPanel Margin="5">
            <Button x:Name="Btn_Start" Content="Start" Click="Btn_Start_Click" />
            <TextBlock x:Name="TblAnzeige" Text="0" FontSize="20" HorizontalAlignment="Center" />
        </StackPanel>
    </Window>

    In diesem Beispiel wird der Fortschritt der Datenverarbeitungsschritte angezeigt:  Daten verarbeiten: 1...9, aber die "zusätzlichen" Invoke's (Schritt 2 und 5) aktualisieren nicht die Oberfläche! Das sie ausgeführt werden, bemerkt man an der deutlich längeren Ausführungszeit bei diesen Datenverarbeitungsschritten (Thread.Sleep wirkt).

    Wie müsste man es denn gestalten, dass immer die Oberfläche aktualisiert wird???

    Fred.

    Donnerstag, 30. Juli 2020 10:04

Antworten

  • Hi Fred,
    wenn du erreichen willst, dass die Anzeige unabhängig vom Intervall des Eintreffens angezeigt wird, dann baue eine Warteschlange auf, die in einem weiteren Thread verarbeitet wird, z.B. so:

    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    
    namespace WpfApp5
    {
      public partial class MainWindow : Window
      {
        public MainWindow()
        {
          InitializeComponent();
        }
    
        Task DatenVerarbeitungsTask = null;
        private void Btn_Start_Click(object sender, RoutedEventArgs e)
        {
          System.Threading.Tasks.Task.Factory.StartNew(this.Anzeige); // Anzeige-Thread
          this.DatenVerarbeitungsTask = new System.Threading.Tasks.Task(DatenVerarbeitung);
          this.DatenVerarbeitungsTask.Start();  // Datenaktualisierungs-Thread  
        } 
    
        private void DatenVerarbeitung()
        {
          for (int i = 0; i < 10; i++) // 9 Einzel-Vorgänge (a 2s) zur Datenverarbeitung
          {
            SyncAnzeige(1000, "Daten verarbeiten: " + i.ToString());         // Daten verarbeiten (Anzeige)
            System.Threading.Thread.Sleep(2000);        // Datenverarbeitung simulieren
            if (i == 2) SyncAnzeige(2000, "3: 2s warten");               // Bei 3, 2s warten
            if (i == 5) SyncAnzeige(2000, "5: 2s warten");               // Bei 5, 2s warten
          }
          SyncAnzeige(0, "Fertig!");
        }
    
        private void SyncAnzeige(int WarteZeit, string Text)
        {
          Nachrichtenliste.Enqueue(new Nachricht() { WarteZeit = WarteZeit, Text = Text });
          are.Set(); // benachrichtigen, dass etwas auszugeben ist
        }
    
        private AutoResetEvent are = new AutoResetEvent(false);
        private Queue<Nachricht> Nachrichtenliste = new Queue<Nachricht>();
        private void Anzeige()
        {
    
          do
          {
            are.WaitOne(1000); // jede Sekunde testen
            while (Nachrichtenliste.Count > 0)
            {
              var nachricht = Nachrichtenliste.Dequeue();
              System.Windows.Application.Current.Dispatcher.Invoke(() => TblAnzeige.Text = nachricht.Text);
              System.Threading.Thread.Sleep(nachricht.WarteZeit);
            }
          } while (!DatenVerarbeitungsTask.IsCompleted);
        }
    
        internal class Nachricht
        {
          internal int WarteZeit { get; set; }
          internal string Text { get; set; }
        }
    
      }
    }


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks




    • Als Antwort markiert perlfred Donnerstag, 30. Juli 2020 13:21
    • Bearbeitet Peter Fleischer Donnerstag, 30. Juli 2020 17:52
    Donnerstag, 30. Juli 2020 12:34

Alle Antworten

  • Hi Fred,
    mit dem System.Threading.Thread.Sleep(WarteZeit); blockerst du den UI-Tread und damit auch die Aktualisierung der Oberfläche. Da der Hintergrund-Thread aller 2 Sekunden die Oberfläche neu beschreibet und auch die Aktualisierung der Oberfläche 2 Sekunden verzögert wird, ist der Text "2 Sekunden warten" nicht zu sehen, da nach 2 Sekunden bereits wieder "Daten verarbeiten ..." angezeigt wird.

    Beschreibe mal, was du wirklich willst erreichen. Generell sollte man auf Sleep im UI-Thread verzichten. Wenn gewartet werden soll, dann mit "await Task.Delay(...)". Damit wird die Ausführung der laufenden Methode (mit dem await) zwar angehalten, aber das Anhalten blockiert nicht die UI:


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks


    Donnerstag, 30. Juli 2020 11:23
  • Hallo Peter! :-)

    So viel anders ist die produktive Anforderung nicht. Ich habe einen Datentask (async/await) dessen Datenverarbeitungsroutine mehere SQL-Abfragen beinhaltet. Nun möchte ich nach Beendigung einer SQL Abfrage den Fortschritt in einem TextBlock dokumentieren. Damit der Text auch vernünftig gelesen werden kann, möchte ich zusätzlich zu den Datenverarbeitungsschritten eine Pause (zum Lesen) einfügen. (Bei ausgewählten Datenschritten).

    Das Thread.Sleep im Datentask auch den GUI-Task anhält, wusste ich nicht. Ich dachte, das betrifft immer nur den Thread in den man es auch aufruft.

    Ich probiere jetzt mal den Ansatz mit await Task.Delay(nZeit).

    Vielen Dank für deinen Hinweis!

    Fred.

    Donnerstag, 30. Juli 2020 12:01
  • Hi Fred,
    wenn du erreichen willst, dass die Anzeige unabhängig vom Intervall des Eintreffens angezeigt wird, dann baue eine Warteschlange auf, die in einem weiteren Thread verarbeitet wird, z.B. so:

    using System.Collections.Generic;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    
    namespace WpfApp5
    {
      public partial class MainWindow : Window
      {
        public MainWindow()
        {
          InitializeComponent();
        }
    
        Task DatenVerarbeitungsTask = null;
        private void Btn_Start_Click(object sender, RoutedEventArgs e)
        {
          System.Threading.Tasks.Task.Factory.StartNew(this.Anzeige); // Anzeige-Thread
          this.DatenVerarbeitungsTask = new System.Threading.Tasks.Task(DatenVerarbeitung);
          this.DatenVerarbeitungsTask.Start();  // Datenaktualisierungs-Thread  
        } 
    
        private void DatenVerarbeitung()
        {
          for (int i = 0; i < 10; i++) // 9 Einzel-Vorgänge (a 2s) zur Datenverarbeitung
          {
            SyncAnzeige(1000, "Daten verarbeiten: " + i.ToString());         // Daten verarbeiten (Anzeige)
            System.Threading.Thread.Sleep(2000);        // Datenverarbeitung simulieren
            if (i == 2) SyncAnzeige(2000, "3: 2s warten");               // Bei 3, 2s warten
            if (i == 5) SyncAnzeige(2000, "5: 2s warten");               // Bei 5, 2s warten
          }
          SyncAnzeige(0, "Fertig!");
        }
    
        private void SyncAnzeige(int WarteZeit, string Text)
        {
          Nachrichtenliste.Enqueue(new Nachricht() { WarteZeit = WarteZeit, Text = Text });
          are.Set(); // benachrichtigen, dass etwas auszugeben ist
        }
    
        private AutoResetEvent are = new AutoResetEvent(false);
        private Queue<Nachricht> Nachrichtenliste = new Queue<Nachricht>();
        private void Anzeige()
        {
    
          do
          {
            are.WaitOne(1000); // jede Sekunde testen
            while (Nachrichtenliste.Count > 0)
            {
              var nachricht = Nachrichtenliste.Dequeue();
              System.Windows.Application.Current.Dispatcher.Invoke(() => TblAnzeige.Text = nachricht.Text);
              System.Threading.Thread.Sleep(nachricht.WarteZeit);
            }
          } while (!DatenVerarbeitungsTask.IsCompleted);
        }
    
        internal class Nachricht
        {
          internal int WarteZeit { get; set; }
          internal string Text { get; set; }
        }
    
      }
    }


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks




    • Als Antwort markiert perlfred Donnerstag, 30. Juli 2020 13:21
    • Bearbeitet Peter Fleischer Donnerstag, 30. Juli 2020 17:52
    Donnerstag, 30. Juli 2020 12:34
  • Hallo Peter,

    also du zauberst ja immer wieder etwas (für mich) Neues aus dem Hut!!!

    Ich habe es jetzt nachgestellt und es funktioniert für mich perfekt!

    Natürlich muss ich mir es jetzt noch gründlich ansehen, aber es ist ja noch überschaubar.

    Wieder einmal vielen Dank! für die Erweiterung meines Programmier-Horizontes!

    Fred.

    Donnerstag, 30. Juli 2020 13:21
  • Hi Fred,
    im Code war noch ein Fehler. So  muss es richtig sein (ich habe ihn im vorherigen Post korrigiert):

        private void Anzeige()
        {
    
          do
          {
            are.WaitOne(1000); // jede Sekunde testen
            while (Nachrichtenliste.Count > 0)
            {
              var nachricht = Nachrichtenliste.Dequeue();
              System.Windows.Application.Current.Dispatcher.Invoke(() => TblAnzeige.Text = nachricht.Text);
              System.Threading.Thread.Sleep(nachricht.WarteZeit);
            }
          } while (!DatenVerarbeitungsTask.IsCompleted);
        }


    --
    Best Regards / Viele Grüße
    Peter Fleischer (former MVP for Developer Technologies)
    Homepage, Tipps, Tricks

    Donnerstag, 30. Juli 2020 17:16
  • Hallo Peter!

    Das habe ich nicht bemerkt, da der Datenverarbeitungstask scheinbar länger "incompleted" ist als die 1s Wartezeit, bis die Abfrage der Nachrichtenwarteschlange  erneut stattfindet/stattgefunden hatte. 

    Deine Lösung/Beispiel ist für mich sehr lehrreich, da sie mir zeigt, wie man das Handling von Task's in einer Taskwarteschlange aufbauen kann.

    Vielen Dank nochmals!

    Fred.

    Freitag, 31. Juli 2020 09:46