none
WPF, Desktop App.C# ObservableCollection - Befüllen, entfernen, über zwei Wege. RRS feed

  • Frage

  • Hallo,

    mein Ziel ist es diese zwei Listen sicher zu befüllen und Elemente zu entfernen.

     private CollectionViewSource CViewSource1 = new CollectionViewSource();
            private ObservableCollection<Order> ColOrders;


     private ObservableCollection<Material> ColMaterials;
            public ICollectionView View2 { get => CViewSource2.View; }

    Das kann parallel erfolgen.

    Sprich der Benutzer befüllt diese Listen, das Programm entfernt parallel Elemente.

    Wie stelle ich nun sicher, dass das funktioniert und nicht zu Kollisionen führt?

       order = new Order() { OrderNumber = CurrentOrderNumber, OrderQuantity = CurrentOrderQuantity };
    
                    this.ColOrders.Add(order);
                    this.View1.MoveCurrentTo(order);

     if (ColOrders[0].Materials[0].Quantity <= 0)
            {
              Material currentMat = ColOrders[0].Materials[0];
              ColMaterials.Remove(currentMat);
    
              if (ColMaterials.Count == 0)
              {
                   ColOrders.RemoveAt(0);

    Alles innerhalb der gleichen Applikation, also kein Mutex.

    Reicht das, denke eher nicht.

    lock ColOrder = new object()

    //...

    //..

    if (ColMaterials.Count == 0) { lock (lockColOrder) { ColOrders.RemoveAt(0); }


    Danke für Eure Hilfe.

    Grüße Markus

    Mittwoch, 22. Juli 2020 05:22

Antworten

  • Hi Markus,
    ich weiß nicht, was du im Code machst. Hier mal eine einfache Demo mit asynchroner Arbeit mit TCP/IP. Mit dem Instanziieren des ViewModels wird für die Demozwecke auch ein Server gestartet, der dann die Nachrichten des Client annimmt und sofort antwortet. Wie das Nachrichtenspiel bei dir aussieht, hast du bisher verheimlicht, so dass man auch keine passende Demo erstellen kann.

    XAML:

    <Window x:Class="WpfApp1.Window59"
            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:WpfApp59"
            mc:Ignorable="d"
            Title="Window59" Height="450" Width="800">
      <Window.DataContext>
        <local:ViewModel/>
      </Window.DataContext>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto" />
          <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Input, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
        <Button Grid.Row="0" Grid.Column="1" Content="Send" Command="{Binding Cmd}" Margin="5"/>
        <ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding Output}" Margin="5"/>
      </Grid>
    </Window>

    Code:

    using System;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Net;
    using System.Net.Security;
    using System.Net.Sockets;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Input;
    
    namespace WpfApp59
    {
      public class ViewModel
      {
        public ViewModel()
        {
          Output.Insert(0, $"{DateTime.Now:hh:mm:ss.fff} (Thread ID: {Thread.CurrentThread.ManagedThreadId}) - ViewModel instantiated");
          Task.Run(server.Start); // start DemoServer
          Cmd = new RelayCommand((state) => client.Send(Input), (state) => !String.IsNullOrEmpty(Input));
          this.server.Message += MessageEvent;
          this.client.Message += MessageEvent;
        } 
        public string Input { get; set; } = "Test data";
        public ObservableCollection<String> Output { get; set; } = new ObservableCollection<string>();
        public ICommand Cmd { get; }
        private DemoServer server = new DemoServer();
        private DemoClient client = new DemoClient();
        private void MessageEvent(object sender, MessageEventArgs e) => Output.Insert(0, e.Message);
      }
    
      public class DemoClient : BaseClass
      {
        public void Send(string msg)
        {
          RaiseMessage("--- Client will send");
          var client = new TcpClient();
          client.BeginConnect(new IPAddress(host), port, DoSend, new StateInfo() { Client = client, Data = msg });
        }
        private void DoSend(IAsyncResult ar)
        {
          RaiseMessage("Client is connected");
          var state = ar.AsyncState as StateInfo;
          var data = Encoding.ASCII.GetBytes($"{state.Data}{Environment.NewLine}");
          RaiseMessage($"Client send data: {state.Data}");
          using (NetworkStream stream = state.Client.GetStream())
          {
            stream.Write(data, 0, data.Length); // write data to server
            data = new byte[1024]; // prepare buffer
            var i = stream.Read(data, 0, data.Length); // read response
            RaiseMessage($"Client received: {Encoding.ASCII.GetString(data, 0, i)}");
          }
        }
      }
    
      public class DemoServer : BaseClass
      {
        private ManualResetEvent tcpClientConnected = new ManualResetEvent(false); // Thread signal.
        internal void Start()
        {
          var ipAddre = IPAddress.Loopback;
          TcpListener listener = new TcpListener(IPAddress.Any, port);
          listener.Start();
          RaiseMessage("Server is running");
          RaiseMessage($"Server is listening on port {port}");
          //      ' async wait for client
          try
          {
            do
            {
              tcpClientConnected.Reset(); // Set the event to nonsignaled state.
              RaiseMessage("Server is waiting for next connections...");
              listener.BeginAcceptTcpClient(DoAccept, listener);
              tcpClientConnected.WaitOne(); // Wait until a connection Is made And processed before continuing.
            } while (true);
          }
          catch (Exception ex) { RaiseMessage(ex.Message); }
        }
    
        private void DoAccept(IAsyncResult ar)
        {
          byte[] bytes = new byte[1024]; // Buffer for reading data
          var sb = new StringBuilder();
          var listener = ar.AsyncState as TcpListener; // detect server object
          var client = listener.EndAcceptTcpClient(ar); // detect TcpClient
          tcpClientConnected.Set(); // Signal the calling thread to continue.
          using (NetworkStream stream = client.GetStream()) // Get a stream object for reading and writing
          {
            // Loop to receive all the data sent by the client.
            int i = stream.Read(bytes, 0, bytes.Length);
            while (i != 0)
            {
              sb.Append(Encoding.ASCII.GetString(bytes, 0, i)); // ranslate data bytes to a ASCII string.
              if (sb.ToString().Contains(Environment.NewLine)) break;
              i = stream.Read(bytes, 0, bytes.Length); // read next bytes
            }
            RaiseMessage($"Server received: {sb.ToString().Replace(Environment.NewLine,"")}");
            Response(stream, $"Response from Server: {sb.ToString().Replace(Environment.NewLine, "")}"); // Send back a response.
          }
        }
        private void Response(Stream stream, string message)
        {
          byte[] response = Encoding.ASCII.GetBytes(message);
          stream.Write(response, 0, response.Length);
          RaiseMessage($"Server response: {message}");
        }
      }
    
      public class BaseClass
      {
        public event EventHandler<MessageEventArgs> Message;
        private SynchronizationContext sc = SynchronizationContext.Current;
        internal byte[] host = new byte[] { 127, 0, 0, 1 };
        internal int port = 555;
        internal void RaiseMessage(string msg) => sc.Post(new SendOrPostCallback(this.RaiseEvent), $"{DateTime.Now:hh:mm:ss.fff} (Thread ID: {Thread.CurrentThread.ManagedThreadId}) - {msg}");
        private void RaiseEvent(object state) => Message?.Invoke(this, new MessageEventArgs() { Message = state.ToString() });
        internal class StateInfo
        {
          internal TcpClient Client { get; set; }
          internal string Data { get; set; }
        }
      }
    
      public class MessageEventArgs : EventArgs { public string Message { get; set; } }
    
      public class RelayCommand : ICommand
      {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _action;
        public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }
        public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }
        public void Execute(object o) => _action(o);
        public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);
        public event EventHandler CanExecuteChanged
        {
          add { CommandManager.RequerySuggested += value; }
          remove { CommandManager.RequerySuggested -= value; }
        }
      }
    }

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

    Samstag, 25. Juli 2020 07:37
  • Hi Markus,
    in meiner Logger Klasse nutze ich:

            Thread th2 = new Thread(() =>
            {
              do
              {
                if (signalStopCheck.WaitOne(Logger.CheckIntervalHours * 3600000)) break;
                if (new FileInfo(this._fileName).Length > Logger.MaxLogLength) CloseWriter();
                if (this._lastDeleteDate < DateTime.Now.Date)
                {
                  this._lastDeleteDate = DateTime.Now.Date;
                  foreach (FileSystemInfo fsi in (new DirectoryInfo(Logger.LogDirPath)).GetFileSystemInfos())
                    if (fsi.Name.StartsWith(this._nameOfApp) && this._lastDeleteDate.Subtract(fsi.LastWriteTime).TotalDays > Logger.DeleteLogAfterDays) File.Delete(fsi.FullName);
                }
              } while (true);
            });
            th2.Start();

    Da war wahrscheinlich wirklich ein Fehler im Post.


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

    Samstag, 25. Juli 2020 14:20

Alle Antworten

  • Hi Markus,

    mein Ziel ist es diese zwei Listen sicher zu befüllen und Elemente zu entfernen.

    was meinst du mit "sicher befüllen"?

    Das kann parallel erfolgen.

    Ein Erfolg erfolgt immer erfolgreich. Unklar ist, was du damit meinst.

    Sprich der Benutzer befüllt diese Listen, das Programm entfernt parallel Elemente.

    Da gibt es kein Problem, solange die parallel in den unterschiedlichen Threads genutzten Objekte thread-sicher sind. Wenn das nicht der Fall ist, musst du die Zugriffe aus dem Thread, dem die Objekte nicht gehören, in den Thread "umleiten", dem die Objekte gehören. Das kann man mit Dispatcher oder auch SynchronizationContext.Post machen.

    Reicht das, denke eher nicht.

    Ohne das gesamte Konzept und die Realisierung der Parallelarbeit (Zugriffe über Thread-Grenzen und Objekt-Typen)  zu sehen, wird niemand eine verlässliche Aussage machen können.


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


    Mittwoch, 22. Juli 2020 08:08

  • Da gibt es kein Problem, solange die parallel in den unterschiedlichen Threads genutzten Objekte thread-sicher sind. Wenn das nicht der Fall ist, musst du die Zugriffe aus dem Thread, dem die Objekte nicht gehören, in den Thread "umleiten", dem die Objekte gehören. Das kann man mit Dispatcher oder auch SynchronizationContext.Post machen.

    Hallo Peter,

    Du kennst das UserInterface von Dir. Prinzipiell ist das immer noch so.

    Diese Klassen, Objekte verwende ich, um die Aufträge, das Material zu befüllen.

    Jetzt wird das Material abgearbeitet, es wird weniger, bei der Materialmenge <= 0 muss ich das aus der Liste entfernen. 

       ViewModel --- Socketinteface gibt den Event, entferne das Material

    Wenn es wiederum entfernt ist, kann ja wieder über das UserInterface nachgelegt werden.

     Die Frage ist jetzt, kann das zu Problemen führen?

      Wenn ja, wie kann ich das einfach lösen? Nur eine threadsichere Liste?

    user

    Grüße Markus

    Mittwoch, 22. Juli 2020 10:23
  • Hi Markus,
    ich kenne mein Beispiel, was ich dir im answers forum über meine OneDrive bereitgestellt habe. Da gibt es keine weiteren threads zusätzlich zum Main (UI) Thread. Auch gibt es da kein Socketinterface. Wenn dieses Socketinterface bei dir ein Ereignis aus einem anderen Thread liefert, dann musst du für den Zugriff aus der Ereignisroutine auf die nicht thread-sicheren Objekte im UI Thread den Dispatcher (oder SynchronizationContext) bemühen.

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

    Donnerstag, 23. Juli 2020 06:22
  • Hallo Peter,

    kannst Du mir da bitte helfen.

     private CollectionViewSource CViewSource1 = new CollectionViewSource();

    hier gibt es Probleme.

    Application.Current.Dispatcher.Invoke((Action)(() => View1.MoveCurrentTo(order)));

    Dann habe ich das so geschrieben, dann scheint es zu gehen.

    Ich tappe jedoch völlig im Dunkeln ;-)

    Was da im Hintergrund abgeht und ob das so richtig ist. Deine Meinung wäre mir wichtig.

    Warum macht das Microsoft nicht von Haus aus, würde ja der Dispatcher sparen...?

    Danke.

     if (qry == null)
                {
                    order = new Order() { OrderNumber = CurrentOrderNumber, OrderQuantity = CurrentOrderQuantity };
    
                    Application.Current.Dispatcher.Invoke((Action)(() => Slot1Gebinde += "333"));
    
                    this.ColOrders.Add(order);
                    this.View1.MoveCurrentTo(order);  #### hier kracht es!
                }


    start

    sa

     public class ObservableCollectionEx<T> : ObservableCollection<T>
        {
            //https://www.codeproject.com/Tips/414407/Thread-Safe-Improvement-for-ObservableCollection
            // Override the event so this class can access it
            public override event NotifyCollectionChangedEventHandler CollectionChanged;
    
            public ObservableCollectionEx(IEnumerable<T> collection) : base(collection) { }
            public ObservableCollectionEx(List<T> list) : base(list) { }
            public ObservableCollectionEx() : base() { }
    
            protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
            {
                // Be nice - use BlockReentrancy like MSDN said
                using (BlockReentrancy())
                {
                    var eventHandler = CollectionChanged;
                    if (eventHandler != null)
                    {
                        Delegate[] delegates = eventHandler.GetInvocationList();
                        // Walk thru invocation list
                        foreach (NotifyCollectionChangedEventHandler handler in delegates)
                        {
                            var dispatcherObject = handler.Target as DispatcherObject;
                            // If the subscriber is a DispatcherObject and different thread
                            if (dispatcherObject != null && dispatcherObject.CheckAccess() == false)
                                // Invoke handler in the target dispatcher's thread
                                dispatcherObject.Dispatcher.Invoke(DispatcherPriority.DataBind, handler, this, e);
                            else // Execute handler as is
                                handler(this, e);
                        }
                    }
                }
            }
        }


    Grüße Markus



    Donnerstag, 23. Juli 2020 07:22
  • Hi Markus,

    Ich tappe jedoch völlig im Dunkeln ;-)

    Was da im Hintergrund abgeht und ob das so richtig ist. Deine Meinung wäre mir wichtig.

    Warum macht das Microsoft nicht von Haus aus, würde ja der Dispatcher sparen...?

    Mit dieser Beschreibung tappe ich auch völlig im Dunkeln. Ich weiß nicht, welche Programmabschnitte in welchem Thread ausgeführt werden.  Auch kann ich nicht erkennen, welche Objekte zu welchem Thread gehören.

    Der Fehler sagt aus, dass der Getter von View1 aus einem anderen Thread aufgerufen wird als der Thread, in welchem CViewSource1 instanziiert wurde. Mit einem Debugging und dem Thread-Fenster kann man das im Visual Studio problemlos sehen.

    Alle Klassen thread-sicher zu machen, würde die Ausführung total verlangsamen. Dasselbe kann man erreichen, indem man ohne Threads programmiert. Fange doch erst einmal damit an und kapsle später dann die Programmabschnitte, die die Ausführung verlangsamen in eigene Threads, am einfachsten mit der Task-Klasse.


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

    Donnerstag, 23. Juli 2020 10:36
  • Hallo Peter,

    ja es liegt an den Threads.

    Es ist schwierig zu verstehen.

    In der Klasse ViewModel habe ich einen Client.

    Der Server kann zu einem unbestimmten Zeitpunkt was senden, da muss ich dann antworten und genau dort liegt das Problem. Wie kann ich das in den Griff bekommen?

    Ich vermute dass die Empfangsroutine und der Sende Aufruf unterschiedliche Threads sind und das die Problem zu suchen sind. Ja aber wie? Hast noch ein paar Ideen, Ansätze - Task Klasse

    Die Liste mit den Slots(Material). Diese wird von außen gesteuert und herausgelöscht.

    Danach muss ich auf den nächsten Slot schalten.

      Server -> Client --> Signalisiert den Wechsel.

      Client -> Server --> Sendet nächsten Slot.

       public class ViewModel : INotifyPropertyChanged
        {
    ..
    protected XClient ClientXP;
     protected ManualResetEvent EvTCP_NewMessageReceived;
    
    
     ClientXP.OnMessageRun += ClientXP_OnMessageRun;
                ClientXP.OnConnected += ClientXP_OnConnected;
                ClientXP.OnDisconnected += 
    ClientXP_OnDisconnected;
    
    // Empfangsroutine
     private Message.Result ClientXP_OnMessageRun(Sockets.Connection<XMessage>.RunArgs args)
            {
    
    
    
     case "SELECT_MAGAZINE_RES":
                            bool t1 = EvTCP_NewMessageReceived.WaitOne(0);
                            EvTCP_NewMessageReceived.Set();
                            t1 = EvTCP_NewMessageReceived.WaitOne(0);
                            Response_Select_Slot(currentElement);
                            break;
    
    // Je nachdem was ich empfange muss ich eine Antwort senden. Und das funktioniert nicht.
     ClientXPSend(msg);
                if (!EvTCP_NewMessageReceived.WaitOne(Cfg.Timeout))
                {
    
    
    
    
    
    
    
    
    

    Grüße Markus und Danke vorab für Tipps.

    Freitag, 24. Juli 2020 06:20
  • Hi Markus,
    ich würde erst einmal die Beschaffung (Empfangen) und das Versenden in einen Model auslagern, entsprechend MVVM Entwurfsmuster, in welchem der Model für solche Aufgaben vorgesehen ist. Dieser Model ist mit Methoden und Ereignissen zu bestücken, die mit der Geschäftslogik "zusammenarbeiten". Für den Test der Geschäftslogik und der Oberfläche sind zuerst die Methoden im Model mit synchronen Dummy-Abläufen zu füllen (ohne dein ClientXP).

    Wenn das alles funktioniert, dann werden die Dummy-Methoden mit der Kommunikation des ClientXP gefüllt. Wenn du nicht weißt, was ClientXP parallel macht, kapselst du die Kommunikation so, dass Zugriffe aus einem anderen Thread in den UI-Thread (Current SynchronizationContext beim Instanziieren des Model) "umgeleitet" werden, z.B. mit dem Dispatcher oder mit SynchronizationContext.Post oder du nutzt typisierte Task Objekte. Was genau am besten ist, musst du anhand der geforderten und vorhandenen Bedingungen prüfen. Mir scheint, das typisierte Task Objekte am sinnvollsten sind, da du in ihnen das Handshaking seriell abarbeiten kannst und nicht auf ManuelResetEvents zurückgreifen musst.


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

    Freitag, 24. Juli 2020 08:08
  • Wenn das alles funktioniert, dann werden die Dummy-Methoden mit der Kommunikation des ClientXP gefüllt. Wenn du nicht weißt, was ClientXP parallel macht, kapselst du die Kommunikation so, dass Zugriffe aus einem anderen Thread in den UI-Thread (Current SynchronizationContext beim Instanziieren des Model) "umgeleitet" werden, z.B. mit dem Dispatcher oder mit SynchronizationContext.Post oder du nutzt typisierte Task Objekte. Was genau am besten ist, musst du anhand der geforderten und vorhandenen Bedingungen prüfen. Mir scheint, das typisierte Task Objekte am sinnvollsten sind, da du in ihnen das Handshaking seriell abarbeiten kannst und nicht auf ManuelResetEvents zurückgreifen musst.



    Hallo Peter,

    könntest evtl. das an einem Beispiel veranschaulichen?

    Danke.

    Grüße Markus

    Freitag, 24. Juli 2020 09:56
  • Hi Markus,
    ich habe kein XClient. Was soll ich das zeigen? Wie man eine Klasse mit Namen "Model" erstellt und dann im ViewModel mit "new" eine neue Instanz erstellt? Wie man dann Methoden aufruft? Wie man Ereignisse implementiert? Wie man async/await/Task<T> nutzt? Du musst da schon etwas genauer fragen. Am besten ist es Code zu zeigen und die Fehler, die der Code auswirft.

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

    Freitag, 24. Juli 2020 11:04
  • Hallo Peter,

    ja klar, es ist für Dich nicht einfach.

    Also ich empfange die Messages mit der ThreadID = 7

    Senden tue ich unter der ID = 1

    Wenn ich jetzt was empfange und dort Sende bin ich ja auch in der ThreadID = 7 drin.

    Es klappt alles, solange ich auf ein Incoming Call nicht sofort eine Antwort sende.

    Selbst wenn ich jetzt den Socket in einen Thread auslagere, ändert sich ja nichts.

    Die Empfangsroutine hat ja bereits einen Thread.

    ------

       protected ManualResetEvent EvTCP_NewMessageReceived;

    Ich empfange es ja noch, es scheint jedoch so zu sein, dass der ManualResetEvent nicht gesetzt wird.

    24.07.20 21:40:44.9782 [ViewModel] [Client_OnMessageRun].............. Current element= <MAGAZINE_STATE l_magazine="3" color="" background="0" />
    24.07.20 21:40:46.0119 [ViewModel] [Client_OnMessageRun].............. ####ThreadID= 7
    24.07.20 21:40:46.0119 [ViewModel] [Client_OnMessageRun].............. Message= <ROOT>
      <MAGAZINE_STATE l_magazine="3" color="" background="1" />
    </ROOT>
    24.07.20 21:40:46.0119 [ViewModel] [Client_OnMessageRun].............. Current element= <MAGAZINE_STATE l_magazine="3" color="" background="1" />
    24.07.20 21:40:46.9534 [ViewModel] [Client_OnMessageRun].............. ####ThreadID= 7
    24.07.20 21:40:46.9534 [ViewModel] [Client_OnMessageRun].............. Message= <ROOT>
      <MAGAZINE_STATE l_magazine="1" color="" background="0" />
    </ROOT>
    24.07.20 21:40:46.9534 [ViewModel] [Client_OnMessageRun].............. Current element= <MAGAZINE_STATE l_magazine="1" color="" background="0" />
    24.07.20 21:40:47.2735 [ViewModel] [Client_OnMessageRun].............. ####ThreadID= 7
    24.07.20 21:40:47.2735 [ViewModel] [Client_OnMessageRun].............. Message= <ROOT>
      <MAGAZINE_STATE l_magazine="1" color="" background="1" />
    </ROOT>
    24.07.20 21:40:47.2735 [ViewModel] [Client_OnMessageRun].............. Current element= <MAGAZINE_STATE l_magazine="1" color="" background="1" />
    24.07.20 21:40:50.3308 [ViewModel] [ClientSendTo]..................... ####ThreadID= 1
    24.07.20 21:40:50.3308 [ViewModel] [ClientSendTo].....................<ROOT>
      <SELECT_MAGAZINE_REQ l_magazine="3" />
    </ROOT>
    24.07.20 21:40:50.3378 [ViewModel] [Client_OnMessageRun].............. ####ThreadID= 7
    24.07.20 21:40:50.3378 [ViewModel] [Client_OnMessageRun].............. Message= <ROOT>
      <SELECT_MAGAZINE_RES l_magazine="3" s_result="OK" />
    </ROOT>
    24.07.20 21:40:50.3378 [ViewModel] [Client_OnMessageRun].............. Current element= <SELECT_MAGAZINE_RES l_magazine="3" s_result="OK" />

    Ja vielleicht kannst noch was sagen, sicher nicht einfach. 

    Grüße Markus

    Freitag, 24. Juli 2020 20:27
  • Hi Markus,
    ich weiß nicht, was du im Code machst. Hier mal eine einfache Demo mit asynchroner Arbeit mit TCP/IP. Mit dem Instanziieren des ViewModels wird für die Demozwecke auch ein Server gestartet, der dann die Nachrichten des Client annimmt und sofort antwortet. Wie das Nachrichtenspiel bei dir aussieht, hast du bisher verheimlicht, so dass man auch keine passende Demo erstellen kann.

    XAML:

    <Window x:Class="WpfApp1.Window59"
            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:WpfApp59"
            mc:Ignorable="d"
            Title="Window59" Height="450" Width="800">
      <Window.DataContext>
        <local:ViewModel/>
      </Window.DataContext>
      <Grid>
        <Grid.ColumnDefinitions>
          <ColumnDefinition/>
          <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <Grid.RowDefinitions>
          <RowDefinition Height="Auto" />
          <RowDefinition/>
        </Grid.RowDefinitions>
        <TextBox Grid.Row="0" Grid.Column="0" Text="{Binding Input, UpdateSourceTrigger=PropertyChanged}" Margin="5"/>
        <Button Grid.Row="0" Grid.Column="1" Content="Send" Command="{Binding Cmd}" Margin="5"/>
        <ListBox Grid.Row="1" Grid.Column="0" Grid.ColumnSpan="2" ItemsSource="{Binding Output}" Margin="5"/>
      </Grid>
    </Window>

    Code:

    using System;
    using System.Collections.ObjectModel;
    using System.IO;
    using System.Net;
    using System.Net.Security;
    using System.Net.Sockets;
    using System.Security.Cryptography.X509Certificates;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows;
    using System.Windows.Input;
    
    namespace WpfApp59
    {
      public class ViewModel
      {
        public ViewModel()
        {
          Output.Insert(0, $"{DateTime.Now:hh:mm:ss.fff} (Thread ID: {Thread.CurrentThread.ManagedThreadId}) - ViewModel instantiated");
          Task.Run(server.Start); // start DemoServer
          Cmd = new RelayCommand((state) => client.Send(Input), (state) => !String.IsNullOrEmpty(Input));
          this.server.Message += MessageEvent;
          this.client.Message += MessageEvent;
        } 
        public string Input { get; set; } = "Test data";
        public ObservableCollection<String> Output { get; set; } = new ObservableCollection<string>();
        public ICommand Cmd { get; }
        private DemoServer server = new DemoServer();
        private DemoClient client = new DemoClient();
        private void MessageEvent(object sender, MessageEventArgs e) => Output.Insert(0, e.Message);
      }
    
      public class DemoClient : BaseClass
      {
        public void Send(string msg)
        {
          RaiseMessage("--- Client will send");
          var client = new TcpClient();
          client.BeginConnect(new IPAddress(host), port, DoSend, new StateInfo() { Client = client, Data = msg });
        }
        private void DoSend(IAsyncResult ar)
        {
          RaiseMessage("Client is connected");
          var state = ar.AsyncState as StateInfo;
          var data = Encoding.ASCII.GetBytes($"{state.Data}{Environment.NewLine}");
          RaiseMessage($"Client send data: {state.Data}");
          using (NetworkStream stream = state.Client.GetStream())
          {
            stream.Write(data, 0, data.Length); // write data to server
            data = new byte[1024]; // prepare buffer
            var i = stream.Read(data, 0, data.Length); // read response
            RaiseMessage($"Client received: {Encoding.ASCII.GetString(data, 0, i)}");
          }
        }
      }
    
      public class DemoServer : BaseClass
      {
        private ManualResetEvent tcpClientConnected = new ManualResetEvent(false); // Thread signal.
        internal void Start()
        {
          var ipAddre = IPAddress.Loopback;
          TcpListener listener = new TcpListener(IPAddress.Any, port);
          listener.Start();
          RaiseMessage("Server is running");
          RaiseMessage($"Server is listening on port {port}");
          //      ' async wait for client
          try
          {
            do
            {
              tcpClientConnected.Reset(); // Set the event to nonsignaled state.
              RaiseMessage("Server is waiting for next connections...");
              listener.BeginAcceptTcpClient(DoAccept, listener);
              tcpClientConnected.WaitOne(); // Wait until a connection Is made And processed before continuing.
            } while (true);
          }
          catch (Exception ex) { RaiseMessage(ex.Message); }
        }
    
        private void DoAccept(IAsyncResult ar)
        {
          byte[] bytes = new byte[1024]; // Buffer for reading data
          var sb = new StringBuilder();
          var listener = ar.AsyncState as TcpListener; // detect server object
          var client = listener.EndAcceptTcpClient(ar); // detect TcpClient
          tcpClientConnected.Set(); // Signal the calling thread to continue.
          using (NetworkStream stream = client.GetStream()) // Get a stream object for reading and writing
          {
            // Loop to receive all the data sent by the client.
            int i = stream.Read(bytes, 0, bytes.Length);
            while (i != 0)
            {
              sb.Append(Encoding.ASCII.GetString(bytes, 0, i)); // ranslate data bytes to a ASCII string.
              if (sb.ToString().Contains(Environment.NewLine)) break;
              i = stream.Read(bytes, 0, bytes.Length); // read next bytes
            }
            RaiseMessage($"Server received: {sb.ToString().Replace(Environment.NewLine,"")}");
            Response(stream, $"Response from Server: {sb.ToString().Replace(Environment.NewLine, "")}"); // Send back a response.
          }
        }
        private void Response(Stream stream, string message)
        {
          byte[] response = Encoding.ASCII.GetBytes(message);
          stream.Write(response, 0, response.Length);
          RaiseMessage($"Server response: {message}");
        }
      }
    
      public class BaseClass
      {
        public event EventHandler<MessageEventArgs> Message;
        private SynchronizationContext sc = SynchronizationContext.Current;
        internal byte[] host = new byte[] { 127, 0, 0, 1 };
        internal int port = 555;
        internal void RaiseMessage(string msg) => sc.Post(new SendOrPostCallback(this.RaiseEvent), $"{DateTime.Now:hh:mm:ss.fff} (Thread ID: {Thread.CurrentThread.ManagedThreadId}) - {msg}");
        private void RaiseEvent(object state) => Message?.Invoke(this, new MessageEventArgs() { Message = state.ToString() });
        internal class StateInfo
        {
          internal TcpClient Client { get; set; }
          internal string Data { get; set; }
        }
      }
    
      public class MessageEventArgs : EventArgs { public string Message { get; set; } }
    
      public class RelayCommand : ICommand
      {
        private readonly Predicate<object> _canExecute;
        private readonly Action<object> _action;
        public RelayCommand(Action<object> action) { _action = action; _canExecute = null; }
        public RelayCommand(Action<object> action, Predicate<object> canExecute) { _action = action; _canExecute = canExecute; }
        public void Execute(object o) => _action(o);
        public bool CanExecute(object o) => _canExecute == null ? true : _canExecute(o);
        public event EventHandler CanExecuteChanged
        {
          add { CommandManager.RequerySuggested += value; }
          remove { CommandManager.RequerySuggested -= value; }
        }
      }
    }

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

    Samstag, 25. Juli 2020 07:37
  • Hi Peter,

    ich Danke Dir.

    Sieht gut aus. Ich schau's mir genauer an! Musste noch Casten -- // start DemoServer.

    DANKE

    Am Rande, Dein Logger ist richtig gut und für das hilfreich.

    Ich musste den abändern, hast Du den bei Dir getestet?

    //SignalStopped.WaitOne();   #######

    Das muss aus meiner Sicht raus. Oder hat das eine Wirkung?

    Thread th2 = new Thread(() =>
                    {
                        do
                        {
                            //if (!SignalStopCheck.WaitOne(Logger.CheckIntervalHours * 3600000))
                            //    break;
                            if (SignalStopCheck.WaitOne(Logger.CheckIntervalHours * 1000)) // 3600000))
                                break;
     
                            if (new FileInfo(this._FileName).Length > Logger.MaxLogLength)
                                CloseWriter();
     
    // ……
    internal void Write(string message)
                {
                    MessageQueue.Enqueue($"{DateTime.Now:dd.MM.yy HH:mm:ss.ffff} {message}");
                    SignalMessage.Set();
                }
     
                ~LogIntern()
                {
                    Close();
                }
     
                public void Close()
                {
                    Stopping = true;
                    SignalStopCheck.Set();
                    SignalMessage.Set();
                    Thread.Sleep(10);
                    //SignalStopCheck.Set();
                    //SignalStopped.WaitOne();   #######
                }

    Grüße Markus





    Samstag, 25. Juli 2020 09:08
  • Hi Markus,
    ich habe in der Demo genutzt: Visual Studio 2019 16.6.5, Framework 4.8. Wenn du alte Versionen nutzt, kann das u.U. Anpassungen erfordern. Wenn du Demos haben willst, solltest du auch angeben, in welcher alten Version du dies wünschst.

    Ich habe keinen Logger in der Demo. Da wird nur in eine Liste geschrieben (Output), den letzten Eintrag zuoberst.

    Was du mit SignalStopped meinst, ist unklar. In der Demo gibt es eine derartige Variable nicht. Oder meinst du damit meine auf GitHub veröffentlichte Logger Demo?


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

    Samstag, 25. Juli 2020 11:26
  • Was du mit SignalStopped meinst, ist unklar. In der Demo gibt es eine derartige Variable nicht. Oder meinst du damit meine auf GitHub veröffentlichte Logger Demo?


    --

    Hallo Peter,

    ich meine im englischen Forum hast Du einen Screenshot gepostet. Diesen habe ich verwendet und habe Änderungen vorgenommen. Dabei fielen mir zwei Unstimmigkeiten fest.

    https://docs.microsoft.com/en-us/answers/questions/44731/log-assembly-for-c.html

    //SignalStopped.WaitOne(); #######

    und müsste doch so sein, also nicht negiert.

      if (SignalStopCheck.WaitOne(Logger.CheckIntervalHours * 1000)) // 3600000))
                                break;

    >auf GitHub veröffentlichte Logger Demo?

    Hast da einen link?

    Grüße Markus

    Samstag, 25. Juli 2020 14:02
  • Hi Markus,
    in meiner Logger Klasse nutze ich:

            Thread th2 = new Thread(() =>
            {
              do
              {
                if (signalStopCheck.WaitOne(Logger.CheckIntervalHours * 3600000)) break;
                if (new FileInfo(this._fileName).Length > Logger.MaxLogLength) CloseWriter();
                if (this._lastDeleteDate < DateTime.Now.Date)
                {
                  this._lastDeleteDate = DateTime.Now.Date;
                  foreach (FileSystemInfo fsi in (new DirectoryInfo(Logger.LogDirPath)).GetFileSystemInfos())
                    if (fsi.Name.StartsWith(this._nameOfApp) && this._lastDeleteDate.Subtract(fsi.LastWriteTime).TotalDays > Logger.DeleteLogAfterDays) File.Delete(fsi.FullName);
                }
              } while (true);
            });
            th2.Start();

    Da war wahrscheinlich wirklich ein Fehler im Post.


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

    Samstag, 25. Juli 2020 14:20

  • Da war wahrscheinlich wirklich ein Fehler im Post.


    --

    Hallo Peter,

    ok, dann bin ich sicher, dass es passt.

    Dankeschön und Grüße Markus

    Samstag, 25. Juli 2020 16:20