none
MVVM - Daten aus Messanzeige lesen ohne Sleeptime RRS feed

  • Frage

  • Hallo, sorry, dass ich gerade so viele Unklarheiten nacheinander habe aber mich beschäftigt folgendes Thema:

    In einem separeten Thread führe ich eine Comschnittstellen-Kommunikation aus und zwar wie folgt:

            // Server-Liste beginnen zu laden
            public void GetMessdatenBegin()
            {
                // Nutzungszähler hochzählen
                Used++;
                // Delegate für folgende asynchrone Arbeitsweise zuweisen
                AsyncDelegateMesswwerte dMesswerte = new AsyncDelegateMesswwerte(getMessdaten);
                // Asynchrone (in anderem Thread) Verarbeitung starten
                dMesswerte.BeginInvoke(new AsyncCallback(cbMesswert), dMesswerte);           
            }
    
            // eigentliche Laderoutine für die Server-Liste mit Demo-Daten
            public List<Window60Data> getMessdaten()
            {
                // Messwerte abrufen
                SendCommand("Messwert holen", 0x02, Millisekunden, Threshold);
                // warten bis _spManager_NewSerialDataReceived angekommen ist
                //ThreadPool.QueueUserWorkItem(new WaitCallback(_spManager_NewSerialDataRecieved()),waitHandles[0]); TODO funktioniert noch nicht
                SendCommand("Sensorstatus holen", 0x55, Millisekunden, 5);
                SendCommand("Messwert holen", 0x01, Millisekunden, Threshold);
    
                return new List<Window60Data>(new Window60Data[]
                { new Window60Data() { ID = 1, AnzeigenNummer = 01, Messwert = Messwert_X_Live },
                new Window60Data() { ID = 2, AnzeigenNummer = 02, Messwert = Messwert_Y_Live  },
                new Window60Data() { ID = 3, AnzeigenNummer = 55, MesskopfWinkel = LaserkopfWinkel_Live } });
            }
            // Methode, die zum Ende des Laden ausgeführt wird (CallBack)
            private void cbMesswert(IAsyncResult ar)
            {
                // den Delegate aus dem State holen, um die Liste zu erhalten
                AsyncDelegateMesswwerte dMesswerte = (AsyncDelegateMesswwerte)ar.AsyncState;
                // das Ende in den Context des UI-Threads umleiten
                sc.Post(new SendOrPostCallback(cbMesswertEnd), dMesswerte.EndInvoke(ar));
            }
            // Ende des Ladens mit einem Ereignis mitteilen
            private void cbMesswertEnd(object state)
            {
                // Nutzungszähler runterzählen
                Used--;
                // Ereignis auslösen, um dem ViewModel das Ladeende und Ergebnis (Liste) mitzuteilen
                DataArrived?.Invoke(this, new SchnittstelleModelEventArgs()
                { Liste = (List<Window60Data>)state });
            }

    Das störende an der Sache ist nun, dass im Sendcommand ein Thread.Sleep steckt, welcher auf die Antwort des Datenstroms wartet:

    Das Antwortpacket wird anschließend in der Routine

            void _spManager_NewSerialDataRecieved(object sender, SerialDataEventArgs e)
            {
    ...
            }
    

    ausgewertet.

    Der Sleep-Befehl im Sendcommand macht mir Probleme, da trotz extra Thread die Oberfläche blockiert, also stockt.

    Mein Ziel ist es nun mit Waithandle den Sleep-Befehl im Sendcommand zu umgehen. Mir ist allerdings gerade nicht klar wie ich das geschickt machen könnte.

    Habt ihr eine Idee?
    Viele lieben Dank!

    Viele Grüße
    Martin

    Donnerstag, 20. September 2018 10:15

Antworten

  • Hi Martin,
    hier mal eine Demo mit Zustandsautomaten in einem separaten Thread.

    XAML:

    <Window x:Class="WpfApp1.Window73"
            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"
            xmlns:local="clr-namespace:WpfApp1"
            mc:Ignorable="d"
            Title="Window73" Height="450" Width="800">
      <Window.Resources>
        <local:Window73VM x:Key="vm"/>
      </Window.Resources>
        <Grid DataContext="{StaticResource vm}">
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Content="Start" Command="{Binding Cmd}" Margin="5"/>
        <ListBox Grid.Row="1" ItemsSource="{Binding View}"/>
      </Grid>
    </Window>

    Dazu der Code:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Input;
    
    namespace WpfApp1
    {
      public class Window73VM : INotifyPropertyChanged
      {
        /// <summary>
        /// Constructor
        /// </summary>
        public Window73VM()
        {
          model = new Window73Model();
          model.DataArrived += Model_DataArrived;
          cvs.Source = col;
        }
        /// <summary>
        /// Model Instanz
        /// </summary>
        private Window73Model model { get; }
        /// <summary>
        /// Datenquelle für Anzeige
        /// </summary>
        CollectionViewSource cvs = new CollectionViewSource();
        /// <summary>
        /// Auflistung mit Protokolldaten
        /// </summary>
        ObservableCollection<string> col = new ObservableCollection<string>();
        /// <summary>
        /// Sicht auf die Daten
        /// </summary>
        public ICollectionView View { get { return cvs.View; } }
        /// <summary>
        /// Eigenschaft für Command-Bindung
        /// </summary>
        public ICommand Cmd { get { return new RelayCommand(CmdExec); } }
        /// <summary>
        /// Methode, die bei Command-Auslösung abzuarbeiten ist und neuen Client anzeigt
        /// </summary>
        /// <param name="obj">CommandParameter</param>
        private void CmdExec(object obj) { model.Start(); }
        /// <summary>
        /// Ereignisroutine, wenn Model Ergebnisse liefert
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Model_DataArrived(object sender, Window73EventArgs e) { col.Insert(0, e.Info); }
    
        #region  OnPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propName = "") =>
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        #endregion
      }
      internal class Window73Model
      {
        /// <summary>
        /// Contructor, der sich aktuellen SynchronizationContext merkt,
        /// externe Komponente instanziiertund Ereigisse abboniert
        /// </summary>
        public Window73Model()
        {
          sc = SynchronizationContext.Current;
          Com = new Window73Extern();
          Com.DataArrived += Com_DataArrived;
        }
        /// <summary>
        /// aktueller SynchronizationContext
        /// </summary>
        SynchronizationContext sc;
        /// <summary>
        /// Ereignis, wenn Daten empfangen wurden
        /// </summary>
        internal event EventHandler<Window73EventArgs> DataArrived;
        /// <summary>
        /// Signalisierung, wenn externes Ereignis eingetreten ist
        /// </summary>
        AutoResetEvent are = new AutoResetEvent(false);
        /// <summary>
        /// maximale Wartezeit auf Antwort von externer Ressource
        /// </summary>
        int timeOut = 30000;
        /// <summary>
        /// Zustand, den der Zustandsautomat als nächstes verarbeiten soll
        /// </summary>
        Window73Zustand zustand = Window73Zustand.Wait;
        /// <summary>
        /// Instanz der externen Komponente
        /// </summary>
        private Window73Extern Com { get; }
        /// <summary>
        /// Puffer für empfangene Daten
        /// </summary>
        private string Data { get; set; }
        /// <summary>
        /// Start des Vorgangs
        /// Wenn Vorgang bereits gestartet, dann sofort beenden
        /// Zustandsautomat in neuem Thread starten
        /// </summary>
        internal void Start()
        {
          // Test, ob Zustandsautomat im Ruhezustand ist, Abbruch, wenn nicht im Ruhezustand
          if (zustand != Window73Zustand.Wait) return;
          // Thread für Zustandsautomaten vorbereiten
          Thread th = new Thread(new ThreadStart(Zustandsautomat));
          // nächsten Zustand, den der Zustandsautomat verarbeiten soll
          zustand = Window73Zustand.Start;
          // Setze, damit Zustandsautomat startet
          are.Set();
          // Start des Zustandsautomaten
          th.Start();
        }
        /// <summary>
        /// Zustandsautomat, der in einer Schleife nach AutoResetEvent.Set 
        /// den aktuellen Zustand verarbeitet
        /// </summary>
        private void Zustandsautomat()
        {
          // Schleife des Automaten
          do
          {
            // Warten auf AutoResetEvent.Set oder TimeOut
            if (!are.WaitOne(timeOut))
            {
              // es ist TimeOut aufgetreten
              OnDataArrived("*** TimeOut");
              zustand = Window73Zustand.Wait;
            }
            // Auswerzung des Zustandes
            switch (zustand)
            {
              case Window73Zustand.Start:
                zustand = Window73Zustand.Send;
                Com.SendCommand();
                break;
              case Window73Zustand.Send:
                if (Data == "OK")
                {
                  zustand = Window73Zustand.Receive;
                  Com.ReceiveCommand();
                }
                break;
              case Window73Zustand.Receive:
                OnDataArrived(Data);
                zustand = Window73Zustand.Wait;
                break;
              default:
                break;
            }
          } while (zustand != Window73Zustand.Wait);
        }
        /// <summary>
        /// Antwort der externen Komponente, die das externe Gerät bedient
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e">beinhalte auch die empfangenen Daten/Rückmeldungen</param>
        private void Com_DataArrived(object sender, Window73EventArgs e)
        {
          // empfangene Daten puffern.
          Data = e.Info;
          // Zustandsautomaten zur weiteren Arbeit anstoßen.
          are.Set();
        }
        /// <summary>
        /// DataArrived im Context des UI-Threads auslösen, 
        /// um Nachricht nach "oben" zu melden
        /// </summary>
        /// <param name="msg">Nachricht, die gemeldet wird</param>
        private void OnDataArrived(string msg) => sc.Post(new SendOrPostCallback(OnDataArrivedCB), msg);
        /// <summary>
        /// DataArrived aufrufen (jetzt schon im UI-Thread)
        /// </summary>
        /// <param name="state"></param>
        private void OnDataArrivedCB(object state) =>
           DataArrived?.Invoke(this, new Window73EventArgs() { Info = state.ToString() });
      }
      /// <summary>
      /// Beispiel einer Klasse, die externe Geräte (Komponenten) bedient,
      /// per Befehl das externe Gerät "anstößt"
      /// und verzögert (asynchron) vom Gerät eine Antwort erhält.
      /// Im Beipiel sind 2 unterstiedliche Befehle enthalten.
      /// </summary>
      internal class Window73Extern
      {
        /// <summary>
        /// Ereignis, was die Ereignisse der externen Komponenten "weiterleitet"
        /// </summary>
        internal event EventHandler<Window73EventArgs> DataArrived;
        /// <summary>
        /// Beispiel eines Befehls, welches die externe Komponente z.B. startet.
        /// </summary>
        internal void SendCommand() => (new Thread(new ThreadStart(ComArbeit1))).Start();
        /// <summary>
        /// Simulation der Verzögerung der Antwort der externen Komponente mit "OK".
        /// </summary>
        private void ComArbeit1()
        {
          Thread.Sleep(1000);
          DataArrived?.Invoke(this, new Window73EventArgs() { Info = "OK" });
        }
        /// <summary>
        /// Beispiel eines Befehls, welches die externe Komponente zum Senden von Daten "auffordert"
        /// </summary>
        internal void ReceiveCommand() => (new Thread(new ThreadStart(ComArbeit2))).Start();
        /// <summary>
        /// Simulation der Verzögerung der Antwort der externen Komponente,
        /// die mit der aktuellen Zeit antwortet.
        /// </summary>
        private void ComArbeit2()
        {
          Thread.Sleep(1000);
          DataArrived?.Invoke(this, new Window73EventArgs() { Info = $"Data {DateTime.Now.ToLongTimeString()}" });
        }
      }
      /// <summary>
      /// Aufzählung für die Zustände des Zustandsautomaten
      /// </summary>
      enum Window73Zustand
      {
        Wait,
        Start,
        Send,
        Receive
      }
      /// <summary>
      /// Ereignisklasse
      /// </summary>
      internal class Window73EventArgs : EventArgs { internal string Info { get; set; } }
    }


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Samstag, 22. September 2018 15:33

Alle Antworten

  • Hi,

    ich kann dein Problem auf Anhieb nicht nachvollziehen, allerdings dein Programm auch nicht.... Könnte aber auch an mir liegen ;)

    Ich habe aber mal eine kleine Demo erstellt, in welcher die Oberfläche nicht blockiert wird. In der Demo sind auch meine Vorschläge deiner anderen Frage.

    Gruß Stefan


    Freiberufler im Bereich Softwareentwicklung Von der PLC und Robotik zu VB.NET & C#, vorrangig WPF und UWP

    Donnerstag, 20. September 2018 13:59
  • Hi Martin,
    Dein Konzept kann so nicht richtig funktionieren.

    Du startest zwar in einem separaten Thread das Senden/Empfangen, führst aber das Senden/Empfagen synchron aus. d.h. Du startest das Senden, blockierst den Thread (Thread.Sleep im SendCommand), holst dann die Antwort und übergibst die Antwort zu Thread-Ende (cbMesswert) über Post an den Haupt-Thread.

    Die Technologie sollte eine andere sein.

    1. Du startest eine Send-Begin-Funktion, die einen Thread startet und danach sofort beendet wird und ein Status-Objekt liefert, welches, falls dafür Bedarf besteht, abgefragt werden kann, um zu ermitteln, ob das Send noch in Arbeit ist.

    2. Im Thread wird das SendCommand gestartet (!nur gestartet) und danach die Methode (der Thread) beendet. Der Thread selbst bleibt im System noch aktiv, da ja die Com-Schnittstelle noch aktiv ist und daran das DataReceived-Ereignis hängt.

    3. Sobald DataReceived eintritt, weil Daten im Puffer angekommen sind, wird geprüft, ob alles angekommen ist. Wenn alles angekommen ist, dann wird das Ereignis "DataArrived" ausgelöst und mit SendCommand die Schnittstelle in einen Ausgangszustand (Ruhezustand) versetzt.

    Bei dieser Arbeitsweise wird kein Sleep benötigt, da der Zyklus Senden-Empfangen "aufgebrochen" ist  (wartend). Empfangsereignisse führen mit "SerialDataReceived" zur "Reaktivierung" des Programmes. 


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Freitag, 21. September 2018 17:32
  • Hi Martin,
    hier mal eine Demo mit Zustandsautomaten in einem separaten Thread.

    XAML:

    <Window x:Class="WpfApp1.Window73"
            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"
            xmlns:local="clr-namespace:WpfApp1"
            mc:Ignorable="d"
            Title="Window73" Height="450" Width="800">
      <Window.Resources>
        <local:Window73VM x:Key="vm"/>
      </Window.Resources>
        <Grid DataContext="{StaticResource vm}">
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto"/>
          <RowDefinition/>
        </Grid.RowDefinitions>
        <Button Content="Start" Command="{Binding Cmd}" Margin="5"/>
        <ListBox Grid.Row="1" ItemsSource="{Binding View}"/>
      </Grid>
    </Window>

    Dazu der Code:

    using System;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    using System.Threading;
    using System.Windows;
    using System.Windows.Data;
    using System.Windows.Input;
    
    namespace WpfApp1
    {
      public class Window73VM : INotifyPropertyChanged
      {
        /// <summary>
        /// Constructor
        /// </summary>
        public Window73VM()
        {
          model = new Window73Model();
          model.DataArrived += Model_DataArrived;
          cvs.Source = col;
        }
        /// <summary>
        /// Model Instanz
        /// </summary>
        private Window73Model model { get; }
        /// <summary>
        /// Datenquelle für Anzeige
        /// </summary>
        CollectionViewSource cvs = new CollectionViewSource();
        /// <summary>
        /// Auflistung mit Protokolldaten
        /// </summary>
        ObservableCollection<string> col = new ObservableCollection<string>();
        /// <summary>
        /// Sicht auf die Daten
        /// </summary>
        public ICollectionView View { get { return cvs.View; } }
        /// <summary>
        /// Eigenschaft für Command-Bindung
        /// </summary>
        public ICommand Cmd { get { return new RelayCommand(CmdExec); } }
        /// <summary>
        /// Methode, die bei Command-Auslösung abzuarbeiten ist und neuen Client anzeigt
        /// </summary>
        /// <param name="obj">CommandParameter</param>
        private void CmdExec(object obj) { model.Start(); }
        /// <summary>
        /// Ereignisroutine, wenn Model Ergebnisse liefert
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void Model_DataArrived(object sender, Window73EventArgs e) { col.Insert(0, e.Info); }
    
        #region  OnPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propName = "") =>
          PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propName));
        #endregion
      }
      internal class Window73Model
      {
        /// <summary>
        /// Contructor, der sich aktuellen SynchronizationContext merkt,
        /// externe Komponente instanziiertund Ereigisse abboniert
        /// </summary>
        public Window73Model()
        {
          sc = SynchronizationContext.Current;
          Com = new Window73Extern();
          Com.DataArrived += Com_DataArrived;
        }
        /// <summary>
        /// aktueller SynchronizationContext
        /// </summary>
        SynchronizationContext sc;
        /// <summary>
        /// Ereignis, wenn Daten empfangen wurden
        /// </summary>
        internal event EventHandler<Window73EventArgs> DataArrived;
        /// <summary>
        /// Signalisierung, wenn externes Ereignis eingetreten ist
        /// </summary>
        AutoResetEvent are = new AutoResetEvent(false);
        /// <summary>
        /// maximale Wartezeit auf Antwort von externer Ressource
        /// </summary>
        int timeOut = 30000;
        /// <summary>
        /// Zustand, den der Zustandsautomat als nächstes verarbeiten soll
        /// </summary>
        Window73Zustand zustand = Window73Zustand.Wait;
        /// <summary>
        /// Instanz der externen Komponente
        /// </summary>
        private Window73Extern Com { get; }
        /// <summary>
        /// Puffer für empfangene Daten
        /// </summary>
        private string Data { get; set; }
        /// <summary>
        /// Start des Vorgangs
        /// Wenn Vorgang bereits gestartet, dann sofort beenden
        /// Zustandsautomat in neuem Thread starten
        /// </summary>
        internal void Start()
        {
          // Test, ob Zustandsautomat im Ruhezustand ist, Abbruch, wenn nicht im Ruhezustand
          if (zustand != Window73Zustand.Wait) return;
          // Thread für Zustandsautomaten vorbereiten
          Thread th = new Thread(new ThreadStart(Zustandsautomat));
          // nächsten Zustand, den der Zustandsautomat verarbeiten soll
          zustand = Window73Zustand.Start;
          // Setze, damit Zustandsautomat startet
          are.Set();
          // Start des Zustandsautomaten
          th.Start();
        }
        /// <summary>
        /// Zustandsautomat, der in einer Schleife nach AutoResetEvent.Set 
        /// den aktuellen Zustand verarbeitet
        /// </summary>
        private void Zustandsautomat()
        {
          // Schleife des Automaten
          do
          {
            // Warten auf AutoResetEvent.Set oder TimeOut
            if (!are.WaitOne(timeOut))
            {
              // es ist TimeOut aufgetreten
              OnDataArrived("*** TimeOut");
              zustand = Window73Zustand.Wait;
            }
            // Auswerzung des Zustandes
            switch (zustand)
            {
              case Window73Zustand.Start:
                zustand = Window73Zustand.Send;
                Com.SendCommand();
                break;
              case Window73Zustand.Send:
                if (Data == "OK")
                {
                  zustand = Window73Zustand.Receive;
                  Com.ReceiveCommand();
                }
                break;
              case Window73Zustand.Receive:
                OnDataArrived(Data);
                zustand = Window73Zustand.Wait;
                break;
              default:
                break;
            }
          } while (zustand != Window73Zustand.Wait);
        }
        /// <summary>
        /// Antwort der externen Komponente, die das externe Gerät bedient
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e">beinhalte auch die empfangenen Daten/Rückmeldungen</param>
        private void Com_DataArrived(object sender, Window73EventArgs e)
        {
          // empfangene Daten puffern.
          Data = e.Info;
          // Zustandsautomaten zur weiteren Arbeit anstoßen.
          are.Set();
        }
        /// <summary>
        /// DataArrived im Context des UI-Threads auslösen, 
        /// um Nachricht nach "oben" zu melden
        /// </summary>
        /// <param name="msg">Nachricht, die gemeldet wird</param>
        private void OnDataArrived(string msg) => sc.Post(new SendOrPostCallback(OnDataArrivedCB), msg);
        /// <summary>
        /// DataArrived aufrufen (jetzt schon im UI-Thread)
        /// </summary>
        /// <param name="state"></param>
        private void OnDataArrivedCB(object state) =>
           DataArrived?.Invoke(this, new Window73EventArgs() { Info = state.ToString() });
      }
      /// <summary>
      /// Beispiel einer Klasse, die externe Geräte (Komponenten) bedient,
      /// per Befehl das externe Gerät "anstößt"
      /// und verzögert (asynchron) vom Gerät eine Antwort erhält.
      /// Im Beipiel sind 2 unterstiedliche Befehle enthalten.
      /// </summary>
      internal class Window73Extern
      {
        /// <summary>
        /// Ereignis, was die Ereignisse der externen Komponenten "weiterleitet"
        /// </summary>
        internal event EventHandler<Window73EventArgs> DataArrived;
        /// <summary>
        /// Beispiel eines Befehls, welches die externe Komponente z.B. startet.
        /// </summary>
        internal void SendCommand() => (new Thread(new ThreadStart(ComArbeit1))).Start();
        /// <summary>
        /// Simulation der Verzögerung der Antwort der externen Komponente mit "OK".
        /// </summary>
        private void ComArbeit1()
        {
          Thread.Sleep(1000);
          DataArrived?.Invoke(this, new Window73EventArgs() { Info = "OK" });
        }
        /// <summary>
        /// Beispiel eines Befehls, welches die externe Komponente zum Senden von Daten "auffordert"
        /// </summary>
        internal void ReceiveCommand() => (new Thread(new ThreadStart(ComArbeit2))).Start();
        /// <summary>
        /// Simulation der Verzögerung der Antwort der externen Komponente,
        /// die mit der aktuellen Zeit antwortet.
        /// </summary>
        private void ComArbeit2()
        {
          Thread.Sleep(1000);
          DataArrived?.Invoke(this, new Window73EventArgs() { Info = $"Data {DateTime.Now.ToLongTimeString()}" });
        }
      }
      /// <summary>
      /// Aufzählung für die Zustände des Zustandsautomaten
      /// </summary>
      enum Window73Zustand
      {
        Wait,
        Start,
        Send,
        Receive
      }
      /// <summary>
      /// Ereignisklasse
      /// </summary>
      internal class Window73EventArgs : EventArgs { internal string Info { get; set; } }
    }


    --
    Viele Grüsse
    Peter Fleischer (ehem. MVP)
    Meine Homepage mit Tipps und Tricks

    Samstag, 22. September 2018 15:33