Autor |
Beitrag |
OlafSt
      
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Mo 25.08.14 13:30
Hallo Freunde,
ich habe nun mein erstes "ernstes" Projekt in C# begonnen. Die entstehende Software soll sich via TCP mit einem Server unterhalten und dort primär Daten zugestellt bekommen - also nur "konsumieren".
Ich habe sowas schon oft in Delphi gemacht und nun das erste mal das ganze in C# implementiert: Die komplette Kommunikation läuft in einem Hintergrund-Thread, der die Verbindung zum Server aufbaut und bei Verlust wieder herstellt. Die empfangenen Nachrichten werden
- in Delphi in einer TQueue (FIFO-Struktur) gespeichert und dann ein PostMessage ans Hauptfenster gesendet. Ist der Hauptthread ausgelastet, geht nix verloren und bleibt einfach in der Queue stecken, bis sich jemand kümmert
- in C# per Event ans Hauptformular geschickt.
Meine Bitte ist nun: Prüft doch mal die Implementierung meiner C#-Geschichte. Ist der Thread so korrekt aufgebaut ? Kann man das TQueue-Geraffel aus Delphi auch in C# implementieren ? Thread-Safety und -Terminierung ? Macht man das überhaupt alles so in .NET ?
Funktionieren tut der Thread, aber für das erste Mal... Man weiß ja nie. Solche Portierungen haben ja öfters einige Fallstricke inpetto
Danke für die Mühen !
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109: 110: 111: 112:
| using System; using System.Threading; using System.Net; using System.IO; using System.Net.Sockets; using System.Text;
public class SpesenThreadArgs : EventArgs { private string FMessage; public SpesenThreadArgs(string AMsg) { FMessage = AMsg; }
public string Message { get { return FMessage; } } }
public class SpesenThread { private TcpClient TL; private byte[] ReceiveBuffer; private Stream TcpStream;
private string FTargetIP; private int FTargetPort; private volatile bool IsTerminated;
private string Message; public delegate void SpesenEventHandler(SpesenThreadArgs e); public event SpesenEventHandler OnReceiveData;
public SpesenThread(string TargetIP, int TargetPort) { FTargetIP=TargetIP; FTargetPort=TargetPort; ReceiveBuffer = new byte[100]; TL = new TcpClient(); TL.ReceiveTimeout = 250; IsTerminated = false; }
public void OnReceived(SpesenThreadArgs e) { if (OnReceiveData != null) OnReceiveData(e); }
public void Terminate() { IsTerminated = true; }
public void WorkerMethod() { int RcvCount; ASCIIEncoding enc;
enc = new ASCIIEncoding();
while (!IsTerminated) { if (!TL.Connected) { try { TL.Connect(FTargetIP, FTargetPort); TcpStream = TL.GetStream(); } catch { } } else { try { RcvCount = TcpStream.Read(this.ReceiveBuffer, 0, 100); } catch(IOException) { RcvCount = 0; } if (RcvCount > 0) { Message = enc.GetString(ReceiveBuffer, 0, RcvCount); OnReceived(new SpesenThreadArgs(Message)); } } } }
} |
Im Hauptformular steht nur noch:
C#-Quelltext 1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22:
| private void button1_Click(object sender, RoutedEventArgs e) { ST = new SpesenThread("10.0.0.1", 28416); ST.OnReceiveData += OnReceiveData; ts = new ThreadStart(ST.WorkerMethod); TCPThread = new Thread(ts); TCPThread.Start(); } public void OnReceiveData(SpesenThreadArgs e) { MessageBox.Show("Received: " + e.Message); }
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (ST != null) { ST.Terminate(); if (!TCPThread.Join(1000)) TCPThread.Abort(); } } |
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Ralf Jansen
      
Beiträge: 4708
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Mo 25.08.14 14:37
Zitat: | in Delphi in einer TQueue (FIFO-Struktur) |
System.Collection.Generic.Queue<T> oder hier eventuell wegen Crossthread besser gleich System.Collection.Generic.Queue<T> verwenden.
Zitat: | Ist der Thread so korrekt aufgebaut ? |
Kann man so machen. Beachtet das OnReceiveData in diesem Thread ausgeführt wird. Solltest du im EventHandler auf die Form zugreifen wollen dann mußt du noch synchronisieren.
Zitat: | public delegate void SpesenEventHandler(SpesenThreadArgs e); |
Events nie ohne Sender. Wirf die Delgatendefinition weg und benutzt EventHandler<SpesenThreadArgs> als Typ für OnReceivedData.
Teste auf eine konkreten Exceptiontyp (eventuell auf SocketException). Es wäre ziemlicher Mist hier auch OutOfMemory etc. zu schlucken.
In OnReceived den delegaten vor dem aufrufen noch kopieren. Sonst könnte sich in dem Moment jemand an den Event hängen während er ausgeführt wird. (Enumeratoren darf man nicht ändern während der Enumerierung)
C#-Quelltext 1: 2: 3: 4: 5: 6:
| public void OnReceived(SpesenThreadArgs e) { var tmp = OnReceiveData; if (tmp != null) tmp (this, e); } |
Du solltest noch dafür sorgen das der TCPClient beim Thread Ende sauber disposed wird. Daher würde ich das konstruieren und zerstören des TCPClient komplett in die
WorkerMethod verlegen dann kann man mit einem using Block für den TcpClient arbeiten. Wenn du das erzeugen im Construktor erledigst wie jetzt wärst du gezwungen auch für deine SpesenThread Klasse IDisposable zu implementieren um den TcpClient deterministisch zu zerstören.
Und es wäre nett wenn du dich dem C# Naming anpassen würdest  Der Präfix F für Klassenvariablen oder wenn man Klassenvariablen groß schreibt wirkt deplaziert.
Für diesen Beitrag haben gedankt: OlafSt
|
|
Palladin007
      
Beiträge: 1282
Erhaltene Danke: 182
Windows 11 x64 Pro
C# (Visual Studio Preview)
|
Verfasst: Mo 25.08.14 19:42
Zitat: | System.Collection.Generic.Queue<T> oder hier eventuell wegen Crossthread besser gleich System.Collection.Generic.Queue<T> verwenden. |
Sicher dass du das meinst?
Du hast beides mal den gleichen Klassennamen genannt. ^^
Zu dem Code kann ich konkret nicht viel sagen, da ich in dem Thema Netzwerkprogrammierung nie etwas gemacht habe und bei der Event-Programmierung bisher nur wenig.
Ein paar Clean-Code-Dinge sind mir aber doch aufgefallen. Das sind die Clean-Code-Regeln mit denen ich "aufgewachsen" bin. Einzelne Punkte sind sicherlich Geschmackssache, dennoch schreibe ich, was mir aufgefallen ist:
- Properties gehören unter private Variablen und über Konstruktoren. Unter dem Konstruktor landen dann die Methoden.
Außerdem bin ich ein Fan davon, den get-Accessor in eine Zeile zu schreiben: get { return FMessage; }
- Private gloabel variablen sind klein Geschrieben und beginnen mit einem Unterstrich. Lokale Variablen (auch Parameter) sind klein geschrieben und Properties werden groß geschrieben.
So weiß man immer, was in welchem Bereich deklariert wurde, außerdem hast du keine Namenskonflikte, ohne den Namen an sich groß ändern zu müssen.
- Keine Typ-Definitionen in einer Klasse/Struktur. delegate erstellt einen neuen Typ und landet daher außerhalb der Klasse.
Ganz selten ist eine Ausnahme sinnvoll, allerdings waren die einzigen sinnvollen Ausnahmen bei mir bisher nur private Klassen, die einzig und alleine Daten kapseln.
Oder Klassen, die für jeden konkreten Anwendungsfall neu geschrieben werden müssen. Die Enumerator-Klassen sind solche Beispiele (ist aber unnötig, seit yield), ich habe manchmal aber auch eigene Beispiele in dem Stil. Die sind dann so speziell und nur in der Klasse notwendig, daher sind die private und liegen in der Klasse.
Im Zweifelsfall aber möglichst vermeiden.
- Methoden tragen einen Namen, der eine Tätigkeit ausdrückt. Beispiel: WorkerMethod wird zu DoWork
- Methoden sollten nicht länger sein, als Zeilen, die auf den Bildschirm passen. Ich würde sagen, maximal 3/4 von dem vorhandenen Platz.
Ausnahmen gibt es bekantlich immer, aber meistens lassen sich Methoden sauber in Teilaufgaben splitten und lassen sich so leichter lesen.
Beispiel: Die Methode WorkerMethod. Da kannst du bestimmt den Inhalt der while-Schleife aufsplitten oder ganz auslagern.
- Deklarationen bleiben in einer Zeile, wenn es ohne Verlust der Übersichtlichkeit möglich ist.
Beispiel: Die Variable enc in WorkerMethod lässt sich auch so schreiben: var enc = new ASCIIEncoding();
- Schreibe Kommentare nur, wenn sie wirklich notwendig sind und erst, wenn du dir genau überlegt hast, warum sie wirklich notwendig sind.
Faustregel: Guter Code braucht keine Kommentare um verstanden zu werden.
Warum hast du z.B. in WorkerMethod unter if (!TL.Connected) geschrieben ?
Das geblockt ist, sieht man ja auch an der Bedingung und muss nicht extra dazu geschrieben werden. Das Gleiche für den else-Block.
Und dann das
Ich gehe mal davon aus, das war ein Kommentar mehr für uns. Und ja, C# und besonders .NET hat schon so einige coole Sachen. ^^
- Kommentare in englisch. Der Code ist englisch, also auch die Kommentare.
- Manchmal hilft eine Leerzeile, den Code übersichtlicher zu gestalten.
Beispiel: In button1_Click würde ich vor TCPThread = new Thread(ts); eine Leerzeile schreiben, da es vorher um SpesenThread ging und jetzt um etwas Anderes.
Das entscheide ich meist aber nach Gefühl, da habe ich keine feste Regel.
- Benennung sollte so sein, dass sie genau beschreibt, was drin ist. SpesenThread ist keine Thread-Klasse, sie wird aber benutzt um die Funktionen bereit zu stellen, die in einem Thread laufen sollen.
Entweder du wählst einen Namen, der genau das kurz und knackig beschreibt, oder du baust tatsächlich einen Thread ein, den du dann auch dort starten kannst. Ich bevorzuge Letzteres, dann muss sich die Form nicht mit dem Thread herum schlagen.
- Ich bin ein Fan von dem MVVM-Pattern.
Ich würde daher ein ViewModel erstellen, das die GUI-Logik enthält und dann ein Objekt der Form übergeben. Die ruft dann nur noch Methoden auf.
So sind dann auch die Event-Methoden fast leer, du musst dich bei View-Änderungen nicht um die Logik kümmern und umgekehrt und du kannst DataBinding nutzen.
Das ist mir auf gefallen. Ich gebe zu, das ist ziemlich viel und ich bin da sehr pingelig.
Allerdings ist das mein Programmier-Stil und keine Richtlinie für jeden Anderen, du kannst dir da aber gerne abschauen, was für dich sinnvoll erscheint.
Ich kann dazu nur sagen: Ich und das Team, in dem ich arbeite, wir fahren damit bisher sehr gut.
Für diesen Beitrag haben gedankt: OlafSt
|
|
Ralf Jansen
      
Beiträge: 4708
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Mo 25.08.14 20:15
Zitat: | Du hast beides mal den gleichen Klassennamen genannt. ^^ |
Ups Copy&Paste Error  2.tes sollte System.Collections.Concurrent.ConcurrentQueue<T> werden.
|
|
OlafSt 
      
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Di 26.08.14 10:21
Guten Morgen Freunde,
wow, erstmal tausend Dank für die Kritiken. Ich werd sehen, das ich das alles umgesetzt bekomme - besonders die Tatsache, das der Eventhandler noch im SpesenThread-Kontext läuft, habe ich schon vermutet.
Stelle ich das um auf Invoke, so wird dieser Handler ja "umgestellt" auf den Mainthread-Kontext. Aber: Ist dieser blockiert (warum auch immer), dann steht auch mein Empfangsthread -doch wenn der auch feststeht, dann brauche ich im Endeffekt keinen Empfangsthread und auch keine Queue (ConcurrentQueue hatte ich schon mal gelesen - danke fürs Erinnern !). Die Queue dient ja dazu, das die Daten auf sicher erstmal empfangen sind und der verarbeitende Thread (was ja nicht Main sein muß) kümmert sich dann "gelegentlich" drum. In den Delphi-Projekten benutze ich dann PostMessage oder einen TEvent, um das Bereitstellen von Daten anzuzeigen, vergleichbares habe ich in .NET bisher nicht gefunden und Postmessage ist vllt. nicht mehr so gern gesehen in C#
Zum Thema Stil: Wie man merkt bin ich Vollblut-Delphianer, der das schon seit Turbo Pascal 5.5 macht, ergo ist die Naming Convention (das mit dem F und auch die Groß-Kleinschreibung) tief im Hirn eingebrannt. Man möge mir das Nachsehen, ich werde mich bemühen. Ernsthaft. Auch das mit den Kommentaren möge man mir nachsehen - mich hats umgehauen, als ich schon in Gedanken eine Routine bastelte, um aus byte[] einen String zu machen und dann zufällig über das ASCIIEncoding stolperte. Ich bin noch nicht sehr tief ins .NET-Konglomerat eingedrungen und auch die zahllosen Tutorials kratzen da nur sehr vorsichtig an der Oberfläche. Falls also so ein unsinniges Konstrukt auffällt, sofort mit dem Finger drauf zeigen
Oh, und von MVVM und derartigen Methodiken habe ich absolut null Plan und auch nicht den Mut, danach zu fragen.
Ergo: Wenn ich darf, stelle ich den überarbeiteten und korrigierten Code zur Prüfung nochmal hier ein, sobald er fertig ist.
Danke nochmals für eure Mühen !
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Palladin007
      
Beiträge: 1282
Erhaltene Danke: 182
Windows 11 x64 Pro
C# (Visual Studio Preview)
|
Verfasst: Di 26.08.14 12:00
Du musst dich nicht rechtfertiken, du wolltest Kritik und die hast du
So manche Dinge (wie Ralf schon sagte) sind aber in der C#-Welt wichtig um nicht ständig Kommentare dazu hören zu müssen.
Was Klassen angeht, die du nicht kennst:
Gewöhn dich dran
Das .NET-Framework ist sehr groß und dann gibt es noch einige oder große kleine Frameworks drum herum, z.B. das PresentationFramework.
Das lernt man nur mit der Zeit alles kennen und ich denke, auch die alten Hasen haben nicht alles gesehen.
Daher der beste Tipp, den man geben kann: Google ist dein Freund
MVVM:
Das MVVM-Pattern wurde zwar von Microsoft für WPF entworfen, funktioniert aber genauso gut bei allen anderen Programmiersprachen, welche die nötigen Paradigmen unterstützen.
Abgesehen davon ist das MVVM-Pattern allgemein sehr gut für die View-Schicht geeignet. Das MVVM-Pattern und WPF sind zwar aufeinander abgestimmt, es ist aber auch locker mit WinForms möglich und jeder anderen Technik. Es wird halt etwas umständlicher, die Vorteile bleiben aber trotzdem.
In meinen Augen gehört das für Programme, die eine Oberfläche besitzen, genauso dazu, wie die Schichtentrennung und (wenn eine Datenbank dazu gehört) das Repository-Pattern.
Den überarbeiteten Code darfst du natürlich rein stellen 
Für diesen Beitrag haben gedankt: OlafSt
|
|
OlafSt 
      
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Di 26.08.14 14:01
So, ich habe einiges an Umbauten vorgenommen. Nun tauchen zwei Probleme auf. Da wir ja nienmals mehrere probleme zugleich in einem Thread bearbeiten wollen/sollen/dürfen, bearbeiten wir eines nach dem anderen.
Problem 1: Eventhandler in den richtigen Kontext bringen. Ich habe da viel gelesen über Invoke und InvokeRequired, auch etliche beispiele gesehen. Dummerweise ist sowohl Invoke als auch InvokeRequired in meinen Sourcen unbekannt. Auch mit einem this. davor wirds nicht besser.
Ergo:
C#-Quelltext 1: 2: 3: 4: 5:
| public void OnReceiveData(Object sender, SpesenThreadArgs e) { MessageBox.Show("Received: " + e.Message); } |
und
C#-Quelltext 1: 2: 3: 4: 5: 6: 7:
| public void OnReceived(SpesenThreadArgs e) { if (OnReceiveData != null) OnReceiveData(e); } |
Wo ist die Lücke ?
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Ralf Jansen
      
Beiträge: 4708
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Di 26.08.14 14:16
Invoke gehört in die Winforms Welt. In WPF gibts dafür den Dispatcher (der wiederum zum Beispiel eine Invoke Methode hat).
C#-Quelltext 1:
| this.Dispatcher.Invoke(() => this.MeinLiebesLabel.Text = "Received: " + e.Message); |
Für diesen Beitrag haben gedankt: OlafSt
|
|
OlafSt 
      
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Di 26.08.14 15:43
Danke für den Hinweis, Problem gelöst.
Problem Nummer 2: ConcurrentQueue<T>. Bisher löste ich dieses Problem stets gleich: Eine globale Variable vom Typ TQueue, die vom Mainthread erzeugt wird, von beiden Threads aber "beackert" wird, Synchronisation via Critical Section.
Ich lese hier (und in der DP, btw), das globale Variablen verpönt sind. Nun kann ich natürlich eine im SpesenThread deklarieren und erstellen. Dis geht aber nicht mit einem weiteren Using-Statement (ConcurrentQueue hat das IDisposable wohl nicht drin), ergo bekomme ich Probleme mit der Freigabe der Queue.
Wie also lösen die Profis hier dieses Problem ?
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Ralf Jansen
      
Beiträge: 4708
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Di 26.08.14 15:56
Da es keine globalen Variablen gibt können die gar nicht verpönnt sein  Sowas gibts nur in nicht vollständig objektorientierten Sprachen wie c++ und Object Pascal.
Kein IDisposable, kein Problem. Das ist nur wichtig wenn man Resourcen am Wickel hat die nicht wirklich zu .Net gehören. Wie eben der Port der vom TcpClient geöffnet wird. Es ist doof wenn der erst wieder freigeben wird wenn .Net mal Lust dazu hat. Wann der frei wird will man selbst entscheiden. Genauso wie bei Files, DBConnections etc. An einer Queue hängen aber erstmal keine Resourcen die ein Problem darstellen könnten wenn die nicht sofort freigegeben werden (Natürlich hängen da eventl. allgemeine Windows Resourcen dran Handles, Speicher etc. aber darauf achtet der Garbage Collector).
Zitat: | Wie also lösen die Profis hier dieses Problem ? |
Was fragst du da uns
Ich würde vermutlich die Queue von der Thread Klasse verwalten lassen. Das ist hier ein klassisches Producer-Consumer Problem. Und da es üblich ist eher 1 einen Producer aber n Consumer zu haben ist es einfacher das vom Producer verwalten zu lassen. Sollte das auf ein n Producer mit n Consumern hinauslaufen dann würde ich auf einen eigenen Service setzen (Service Locator Pattern) wo sowohl Producer als auch Consumer die zu benutzende Queue abholen können. Und bevor du es merkst ja so ein Service wäre dann auch irgendwie was globales.
|
|
jfheins
      
Beiträge: 918
Erhaltene Danke: 158
Win 10
VS 2013, VS2015
|
Verfasst: Di 26.08.14 21:35
OlafSt hat folgendes geschrieben : | In den Delphi-Projekten benutze ich dann PostMessage oder einen TEvent, um das Bereitstellen von Daten anzuzeigen, vergleichbares habe ich in .NET bisher nicht gefunden und Postmessage ist vllt. nicht mehr so gern gesehen in C#  |
Dazu eine kleine Anmerkung: Es gibt BeginInvoke. Sowohl bei den WinForms als auch beim Dispatcher.
Grob vereinfacht:
Invoke() entspricht SendMessage()
BeginInvoke() entspricht PostMessage()
Falls dir die Namenskonvention noch nicht so leicht von der Hand geht: Es gibt Tools wie Resharper ( www.jetbrains.com/re...license=OPEN_SOURCE) die sowas vereinfachen. Da wird dann der Variablenname unterstrichen und dir wird direkt die Umbenennung angeboten, die dem Schema entspricht. Falls du gerne Doku liest, es gibt da was von Microsoft:
msdn.microsoft.com/e...29002(v=vs.110).aspx
Aber insb. msdn.microsoft.com/e...29043(v=vs.110).aspx und msdn.microsoft.com/e...29012(v=vs.110).aspx
|
|
OlafSt 
      
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Mi 27.08.14 09:16
Guten Morgen, Freunde !
Der Hinweis auf ReSharper ist nett, ich schau mir das mal genauer an. Die Naming Conventions, die jfheins hier gepostet hat, sind sehr interessant. Liege ich doch gar nicht so weit weg davon mit dem, was ich gelernt habe.
Die Sache mit den Ressourcen und using habe ich auch kapiert. Sind Ressourcen des Systems involviert, dann using, sonst nicht.
Das ganze ist nun kein SpesenThread mehr, sondern ein RDLHandler (RDL = Remote Download). Der ursprüngliche Name stammt aus Delphi-Zeiten, wo neue Threads grundsätzlich von der TThread-Klasse abgeleitet werden. Ergo war "SpesenThread" durchaus okay - da wußte ich noch nicht, das in C# der Hase anders läuft.
Ich habe außerdem den Delegaten entfernt, wie vorgeschlagen, und einen EventHandler<T> benutzt. Ich habe einen using-Block eingefügt für den TcpClient, eine ConcurrentQueue<T> eingebaut und das ganze mit einem lock(x) abgesichert - die Queue wird noch nicht korrekt abgearbeitet, ist mir klar  . Durch die Queue brauche ich dann keine EventArgs-Ableitung mehr (eigentlich sind gar keine EventArgs mehr nötig).
Zum Schluß wurden noch die Namen ein wenig angepaßt und die Naming Convention weitgehend umgesetzt.
Hier nun der überarbeitete Code:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109:
| using System; using System.Threading; using System.Net; using System.IO; using System.Net.Sockets; using System.Text; using System.Collections.Concurrent;
public class RDLHandlerArgs : EventArgs { private string Msg; public RDLHandlerArgs(string AMsg) { Msg = AMsg; } public string Message { get { return Msg; } } }
public class RDLHandler { private TcpClient TL; private byte[] ReceiveBuffer; private Stream TcpStream; public ConcurrentQueue<string> Queue; public Object RDLLock;
private string TargetIP; private int TargetPort; private volatile bool IsTerminated;
private string Message; public event EventHandler<RDLHandlerArgs> OnReceiveData;
public RDLHandler(string IP, int Port) { TargetIP=IP; TargetPort=Port; ReceiveBuffer = new byte[100]; IsTerminated = false; }
public void OnReceived(RDLHandlerArgs e) { var tmp = OnReceiveData; if (tmp != null) tmp(this, e); }
public void Terminate() { IsTerminated = true; }
public void DoRDLHandling() { int RcvCount; ASCIIEncoding enc = new ASCIIEncoding();
RDLLock = new Object(); Queue = new ConcurrentQueue<string>();
using (TL = new TcpClient()) { TL.ReceiveTimeout = 250; while (!IsTerminated) { if (!TL.Connected) { try { TL.Connect(TargetIP, TargetPort); TcpStream = TL.GetStream(); } catch (SocketException) { } } else { try { RcvCount = TcpStream.Read(this.ReceiveBuffer, 0, 100); } catch (IOException) { RcvCount = 0; } if (RcvCount > 0) { Message = enc.GetString(ReceiveBuffer, 0, RcvCount); lock (RDLLock) { Queue.Enqueue(Message); } OnReceived(new RDLHandlerArgs(Message)); } } } if (TL.Connected) TcpStream.Close(); } }
} |
Die Methode DoRDLHandling() habe ich nicht weiter auseinandergenommen. Es macht aus meiner Sicht wenig Sinn, 5 Zeilen Code in eine extra Methode auszulagern, zumal der Code nun wirklich nicht schwer zu verstehen ist. In anderen Fällen, wo der Code wirklich ans eingemachte geht, stimme ich aber Palladin007 zu, sowas muß auseinanderdividiert werden.
Das Hauptformular(Einzige Änderung: Invoke):
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41:
| public partial class MainWindow : Window { RDLHandler RDH; ThreadStart ts; Thread TCPThread;
public MainWindow() { InitializeComponent(); }
public void OnReceiveData(Object sender, RDLHandlerArgs e) { this.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal, new Action( delegate() { MessageBox.Show("Received: " + e.Message); } )); }
private void button1_Click(object sender, RoutedEventArgs e) { RDH = new RDLHandler("192.168.0.19", 4226); RDH.OnReceiveData += OnReceiveData;
ts = new ThreadStart(RDH.DoRDLHandling); TCPThread = new Thread(ts); TCPThread.Start(); }
private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { if (RDH != null) { RDH.Terminate(); if (!TCPThread.Join(1000)) TCPThread.Abort(); } } } |
Ich hoffe, das ganze sieht nun nach C# aus und nicht mehr nach Delphi. Auch mit dem Invoke stehe ich noch auf Kriegsfuß, so wirklich kapieren tu ich das Zeug nicht
Nochmals Danke für die Bemühungen !
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Ralf Jansen
      
Beiträge: 4708
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Mi 27.08.14 09:54
Der Lock für die Queue ist überflüssig. Das ist gerade die Aufgabe die die Differenz zwischen ConcurrentQueue und Queue ausmacht. ConcurrentQueue ist schon ThreadSafe.
Wenn du das Locking selbst machst kannst du auch einfach eine Queue nehmen. Was sinnvoller ist hängt ein wenig davon ab wie du und wo du dequeuen willst.
|
|
Palladin007
      
Beiträge: 1282
Erhaltene Danke: 182
Windows 11 x64 Pro
C# (Visual Studio Preview)
|
Verfasst: Do 28.08.14 00:23
Ich hab mir mal ein paar Gedanken gemacht, wie ich den RDLHandler ungefähr schreiben würde:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98:
| public class RDLHandlerEventArgs : EventArgs { private string _message;
public string Message { get { return _message; } }
public RDLHandlerEventArgs(string message) { _message = message; } }
public class RDLHandler { private readonly string _targetIp; private readonly int _targetPort; private volatile bool _isTerminated;
public ConcurrentQueue<string> Queue { get; set; } public bool IsTerminated { get { return _isTerminated; } }
public event EventHandler<RDLHandlerEventArgs> ReceiveDataEvent;
public RDLHandler(string ip, int port) { _targetIp = ip; _targetPort = port; }
public void Terminate() { _isTerminated = true; }
public void DoRDLHandling() { Queue = new ConcurrentQueue<string>();
using (var tcpCLient = new TcpClient() { ReceiveTimeout = 250 }) { NetworkStream tcpStream = null;
while (!IsTerminated) { if (!tcpCLient.Connected) tcpStream = GetNetworkStream(tcpCLient); else ReadMessage(tcpStream); } } }
private NetworkStream GetNetworkStream(TcpClient tcpCLient) { NetworkStream resultStream = null;
try { tcpCLient.Connect(_targetIp, _targetPort); resultStream = tcpCLient.GetStream(); } catch (SocketException) { }
return resultStream; }
private void ReadMessage(Stream stream) { var receiveBuffer = new byte[100]; var receivedBytesCount = 0;
try { receivedBytesCount = stream.Read(receiveBuffer, 0, 100); } catch (IOException) { }
if (receivedBytesCount > 0) { var message = new ASCIIEncoding().GetString(receiveBuffer, 0, receivedBytesCount); Queue.Enqueue(message); OnReceiveDataEvent(message); } }
protected void OnReceiveDataEvent(string message) { var handler = ReceiveDataEvent; if (handler != null) handler(this, new RDLHandlerEventArgs(message)); } } |
Ich finde, das ist jetzt deutlich lesbarer.
Du kannst es dir ja mal anschauen.
Ein Punkt, der vielleicht nicht gleich ersichtlich ist:
TcpClient ruft bei Dispose (was durch using aufgerufen wird) intern auch Dispose für den Stream auf, der bei GetSTream zurück gegeben wird. Daher musst du dich darum nicht mehr kümmern und ich habe es raus genommen.
Ich habe auch mal den Window-Teil umgebaut.
Du verwendest WPF, daher passt MVVM ganz gut. Allerdings musst du da CommandBinding von WPF kennen.
Ich habe es erst einmal ohne gebaut, allerdings ließe sich das einfach in der View integrieren.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: 28: 29: 30: 31: 32: 33: 34: 35: 36: 37: 38: 39: 40: 41: 42: 43: 44: 45: 46: 47: 48: 49: 50: 51: 52: 53: 54: 55: 56: 57: 58: 59: 60: 61: 62: 63: 64: 65: 66: 67: 68: 69: 70: 71: 72: 73: 74: 75: 76: 77: 78: 79: 80: 81: 82: 83: 84: 85: 86: 87: 88: 89: 90: 91: 92: 93: 94: 95: 96: 97: 98: 99: 100: 101: 102: 103: 104: 105: 106: 107: 108: 109:
| public class RelayCommand : ICommand { private readonly Action _execute; private readonly Func<bool> _checkCanExecute;
public event EventHandler CanExecuteChanged { add { CommandManager.RequerySuggested += value; } remove { CommandManager.RequerySuggested -= value; } }
public RelayCommand(Action execute) : this(execute, null) { } public RelayCommand(Action execute, Func<bool> checkCanExecute) { _execute = execute; _checkCanExecute = checkCanExecute; }
public bool CanExecute(object parameter) { var result = true;
if (_checkCanExecute != null) result = _checkCanExecute();
return result; } public void Execute(object parameter) { _execute.Invoke(); } }
public class MainWindowViewModel { private readonly Dispatcher _mainWindowDispatcher; private readonly ICommand _runCommand; private readonly ICommand _stopCommand; private RDLHandler _rdhHandler; private Thread _tcpThread;
public ICommand RunCommand { get { return _runCommand; } } public ICommand StopCommand { get { return _stopCommand; } }
public MainWindowViewModel(Dispatcher mainWindowDispatcher) { if (mainWindowDispatcher == null) throw new ArgumentNullException("mainWindowDispatcher");
_mainWindowDispatcher = mainWindowDispatcher;
_runCommand = new RelayCommand(Run); _stopCommand = new RelayCommand(Stop, () => _rdhHandler != null && _tcpThread != null); }
private void ReceiveData(object sender, RDLHandlerEventArgs e) { Action method = () => MessageBox.Show("Received: " + e.Message); _mainWindowDispatcher.BeginInvoke(DispatcherPriority.Normal, method); } public void Run() { _rdhHandler = new RDLHandler("192.168.0.19", 4226); _rdhHandler.ReceiveDataEvent += ReceiveData;
_tcpThread = new Thread(_rdhHandler.DoRDLHandling); _tcpThread.Start(); } public void Stop() { _rdhHandler.Terminate();
if (!_tcpThread.Join(1000)) _tcpThread.Abort(); } }
public partial class MainWindow : Window { public MainWindowViewModel ViewModel { get { return (MainWindowViewModel)DataContext; } set { DataContext = value; } }
public MainWindow() { InitializeComponent(); }
private void button1_Click(object sender, RoutedEventArgs e) { ViewModel.RunCommand.Execute(null); } private void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { ViewModel.StopCommand.Execute(null); } } |
Die Klasse RelayCommand sorgt nur dafür, dass ich nicht für Run und STop eine eigene Klasse schreiben muss, um ICommand zu implementieren.
Wenn du ein solches Command an z.B. einen Button bindest, dann wird permanent CanExcecute von ICommand aufgerufen und legt fest, ob der Button geklickt werden kann. Wenn jemand darauf klickt und der nicht ausgegraut ist, wird Excecute aufgerufen.
Im Prinzip war es das dann schon. Ich habe hauptsächlich den Inhalt von MainWindow in ein ViewModel ausgelagert.
Du hattest aber ein paar globale Variablen (also global im Raum der Klasse), die nicht nötigen waren.
Die Nutzung ist dann so:
C#-Quelltext 1: 2: 3: 4:
| var mainWindow = new MainWindow(); var mainViewModel = new MainWindowViewModel(mainWindow.Dispatcher); mainViewModel.ViewModel = mainViewModel; mainWindow.Show(); |
Du hast damit den Vorteil, dass du die View unabhängig vom ViewModel geändert werden kann. So kann z.B. ein Designer das machen, während du die ViewModels baust.
Ich bin nur nicht ganz zufrieden, dass ich den Dispatcher brauche, bloß viel mir keine bessere Lösung ein und es ist schon spät.
Ich hoffe, es stört dich nicht, dass ich jetzt so viel von deinem Code umgebaut habe.
Mir geht es dabei eigentlich nur darum, zu zeigen, wie ich es für richtig halte. Langes Erklären ist dabei schon recht umständlich, besonders da manche Lösungswege mir erst beim Programmieren in den Sinn kamen.
Wenn ich irgendwo Fehler gemacht habe, bitte ich um eine kurze Info, ich passe das dann gleich an.
|
|
OlafSt 
      
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Fr 29.08.14 10:13
Ich habe das mal sehr genau beäugt. Und es ist doch erstaunlich, wie unterschiedlich Programmierer-Hirne ticken
Die Palladin007'sche implementierung des RDLHandlers finde ich (persönliche, subjektive Meinung) ausgesprochen undurchsichtig. Selbst winzigste Codeblöcke werden sofort in Methoden ausgelagert. Dadurch sieht das DoRDLHandling() natürlich superkompakt aus, keine Frage. Aber alles ist "versteckt" in anderen Methoden und muß erst gesucht werden - allerdings in so einer kleinen Klasse kein Problem.
Ich habe dazu mal gelernt: Behalte in der Methode, was dazu gehört. Vermeide unnötige Prozeduraufrufe, denn sie kosten sehr viel Zeit. Lagere Codeteile nur dann in Methoden aus, wenn sie mehr als 1x benötigt werden, denn: Siehe zweiter Punkt. Gelten diese Regeln nicht mehr ?
Das MVVM-Beispiel finde ich ebenso aufschlußreich. Zunächst einmal ist es purer Zufall, dieses Projekt ein WPF-Projekt ist  Es war gerade offen, als ich auf die Idee mit dem RDLHandler kam.
Wenn ich meinen Stümpercode mit dem von Palladin007 vergleiche, dann sehe ich einen Größenunterschied von etwa 1:25. 25fache Codegröße - doch wofür, wo ist der Benefit ? Sicherlich, man hat seinen Code von der GUI getrennt. Aber tauscht man die GUI wirklich alle 3 Wochen komplett aus ? Mir ist sowas in den letzten 30 Jahren genau 3x untergekommen und meine Programme leben erschreckend lange; Eine Anfrage auf Portabilität auf andere Systeme hatte ich noch gar nicht in all den Jahren; und hätte ich meinem Boß gesagt "Okay, 10 Tage für die Software und nochmal 35 für die Trennung von der GUI" hätte der die ganz große Knarre rausgeholt xD
Wo ist also der Nutzen daraus ? Das interessiert mich nicht nur aus Programmierersicht - auch aus Sicht des Geschäftsmannes. Womöglich auch ein Thema, das man per Skype diskutieren sollte
Überhaupt WPF... Ich habe in den letzten Tagen massenhaft Tutorials gelesen. Doch alle erzählen mir dasselbe: Das ist XML und ganz toll flexibel. Okay, das habe ich nun echt verinnerlicht. Und wie bringe ich ein Panel dazu, am unteren Fensterrand zu kleben und sich in der Breite stets dem Fenster anzupassen (Statusbar-like) ? Solche Tuts sucht man vergebens...
Dieser Thread hier ist der informativste seit langem und hat mich echt weitergebracht - wäre cool, wenn das so bliebe 
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
Ralf Jansen
      
Beiträge: 4708
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Fr 29.08.14 11:04
Zitat: | Vermeide unnötige Prozeduraufrufe, denn sie kosten sehr viel Zeit |
Dem würde ich nicht unbedingt zustimmen. Lesbarkeit hat für mich eine hohe Notwendigkeit  Tatsächlich unnötiges, klar, sollte man weglassen.
Das sie Zeit kosten .. nun ja ... wann ist das tatsächlich mal relevant? Wen es relevant wird dann fang ich trotzdem mit dem Lesbaren an und verzichte irgendwann drauf wenn ich optimieren muss.
Zitat: | Lagere Codeteile nur dann in Methoden aus, wenn sie mehr als 1x benötigt werden, denn: Siehe zweiter Punkt. Gelten diese Regeln nicht mehr ? |
Wenn der Code nur einmal benötigt wird dann würde ich ihn dann immer noch in eine Methode auslagern wenn es eine abgrenzbare Aufgabe beinhaltet dem ich dann auch einen wirklich erklärenden Methodennamen geben kann. So das sich der Sinn des Codes ergibt ohne das ich in diese ~Unter~ Methode tatsächlich reinschauen muß. Das würde die Lesbarkeit potentiell erhöhen. Wenn es nicht besser geht als den Code in Methoden der Abfolge Methode1, Methode2 u.s.w. die dann scheinbar sinnlose Parameter miteinander austauschen zu ersetzen ist das natürlich nicht hilfreich und sollte man lassen.
Letztlich ist das Maß der Lesbarkeit natürlich ein Problem. Was als lesbar empfunden wird hängt natürlich davon ab wieviel Erfahrung man mit der gerade verwendeten Sprache hat, welche anderen Vorerfahrungen man mit bringt und wieviel Wissen man bereits über das zu lösende Problem hat.
Zum Beispiel habt ihr beide die Methode zum Abbrechen Terminate genannt. Ich würde da ein hartes sofortiges Ende des Threads erwarten. Das ist es aber nicht, der Thread wird sauber abgebrochen und läuft aus. Nach meinem empfinden ist das ein Abort kein Terminate. Euer Code ist ein dreckiger Lügner
Dann Paladin's Methodenauslagerung in DoRDLHandling. GetNetworkStream der Name ist passend. Methode macht genau das wenn mich nicht gerade interessiert wie das genau passiert bin ich nicht genötigt da rein zu schauen uns wenn ich mich dafür interessiere kann ich da rein schauen und die Methode kümmert sich auch nur genau um das Problem. Sowas würde ich definitiv genauso auslagern.
Die andere Methode 'ReadMessage' ... hmh, naja ... was ist eine Message und wenn die Methode behauptet die zu lesen warum kommt da nix raus? ReadMessage wirft einfach zu viele Fragen auf man ist gezwingen eh da rein zu schauen wenn man das liest. Dann kann man die auch weglassen außer einem fällt ein tatsächlich passender Namen ein der die Aufgabe der Methode auch richtig beschreibt und eben keine Fragen aufwirft. Ein Möglichkeit wäre die Methode nur das lesen aus dem Stream machen zu lassen und die Message nach dem jagen durch das ASCIIEncoding als Return Parameter rauszurücken. Also eher
C#-Quelltext 1: 2: 3:
| var message = ReadMessageFromStream(stream); Queue.Enqueue(message); OnReceiveDataEvent(message); |
Wobei ich bei dem Begriff Message immer noch ein Problem habe. Bei Message denkt man(ich) an ein Protokoll also an was strukturiertes. Wenn das aber irgendwas beliebiges ist dann würde ich eher von Data oder so etwas sprechen. Ganz beliebig scheint es aber gar nicht zu sein den wir schicken es ja durch ein ASCIIEncoding wissen also das es ASCII Text ist. Hier wäre irgendwo eine klare Definiton von Message nötig dem man dann auch einen eindeutigen wiedererkennbaren Begriff gibt den man im Source Code dann auch so durchzieht.
|
|
Palladin007
      
Beiträge: 1282
Erhaltene Danke: 182
Windows 11 x64 Pro
C# (Visual Studio Preview)
|
Verfasst: Fr 29.08.14 13:47
Ich werde später nochmal ausführlicher darauf eingehen, warum ich den Code so geschrieben habe, da ich aktuell nicht viel Zeit habe.
Es ist definitiv wichtig, die Namen gut zu wählen, denn sonst ist es tatsächlich von Nachteil, eine Methode zu splitten. Einen guten Namen kann aber immer noch der Entwickler, der sich das ganze ausgedacht hat, am besten finden.
Es hat aber den Vorteil, dass jede Methode schnell erfasst werden kann. Wenn der Name und die Parameter gut gewählt sind, dann braucht man auch den Inhalt der Methode nicht kennen um zu wissen, was sie tut. Es ist dabei ja unwichtig, zu wissen, wie sie es tut.
Bei großen Klassen könnte das tatsächlich ein Nachteil werden, allerdings teile ich zu große Klassen dann auch auf, bzw. ich suche Teilaufgaben, die eine eigene Klasse bekommen könnten.
Das MVVM-Pattern funktioniert prinzipiell mit jeder Oberflächen-Technik, aber mitz WPF am besten.
Mir fallen spontan mehrere Vorteile ein:
- Du kannst ein ViewModel vollautomatisch testen (UnitTests), bei der View ist das nur schwer bis gar nicht möglich
- Du kannst die View von einem Grafiker erstellen lassen und dieser braucht keine umfangreichen Kenntnisse vom Programm-Code
- Pro View können mehrere ViewModels gesetzt und so die Funktionen der View gesplittet werden
- Das Austauschen/Ändern/Erweitern der View ist einfacher, da die Funktionen nicht beachtet werden müssen
Außerdem finde ich, dass das rein logisch zwei Aufgaben sind.
Die View soll anzeigen, leitet die Funktionen aber auch nur weiter, weil das ja wieder eigene Funktionen sind und daher auch ausgelagert werden - mindestens in eigene Methoden.
Das ViewModel bereitet die Daten (das Model) so auf, dass die View sie sofort nutzen kann unter Anderem auch die Funktionen.
Beides sind zwei verschiedene Aufgaben und sollten daher auch getrennt werden.
Aber wenn du dir dieses Vorgehen einmal angewöhnt hast, dann passiert das wie von alleine und geht auch schnell. Das kostet dann keine 25 Tage länger, sondern passiert gleich nebenher.
Was die Zeiten aus wirtschaftlicher Sicht angehen:
So oder so, der Chef mag keine langen Zeiten, was ja auch klar ist. Aber er mag es noch weniger, wenn irgendwann (und sag nicht, das kommt nie, das kannst du nicht wissen) irgendwelche so gravierende Änderungen vorgenommen werden müssen, dass du überall anüpacken musst, dann bist du für jede zusätzliche Stunde, die du dir über eine konkrete und notgedrungen auch umfangreichere Architektur gemacht hast, dankbar.
Eine gute Architektur zeichnet sich nämlich nicht dadruch aus, dass sie klein ist. Die Liste der Regeln ist lang und keine davon ist absolut immer gültig. Das ist ja auch die eigentliche Schwierigkeit bei der Softwareentwicklung - das Programm so zu organisieren, dass es auch nach 10 Jahren ständigen Erweiterungen noch organisiert ist.
Irgendwann rächen sich die Langzeitschulden nämlich, selbst wenn sie ursprünglich nur ganz klein aussahen.
Und DANN, wenn du DANN versuchst, deinen Chef zu erklären, warum du die ganze View-Ebene neu aufsetzen musst, da dort ein totales Chaos herscht (Langzeitfehler, Stress, etc.), dann möchte ich mal sein Gesicht sehen. Ich kenne das, wir haben aktuell ein ähnliches Problem und wir dürfen nicht aufräumen, weil die Software weiter kommen muss und Aufräumen sehr viel Zeit kostet. Das größte Pech ist dann auch noch ein Chef, der keinen Einblick in die ganze Problematik hat und sie daher nicht versteht.
Noch zu WPF:
Ja, WPF ist gewöhnungsbedürftig und ja, man muss sich rein gewöhnen, aber wenn du das ganze Konzept einmal verstanden hast, wirst du es mögen
Was du da alles machen kannst und dann auch noch so viel in einer kurzen Zeile Code, das macht einfach Spaß. ^^
Außerdem ist die Darstellung komplett vom Rest getrennt, du kannst einem Button ein völlig eigenes Aussehen geben, wenn du möchtest.
|
|
Ralf Jansen
      
Beiträge: 4708
Erhaltene Danke: 991
VS2010 Pro, VS2012 Pro, VS2013 Pro, VS2015 Pro, Delphi 7 Pro
|
Verfasst: Fr 29.08.14 14:22
Zitat: | Ja, WPF ist gewöhnungsbedürftig und ja, man muss sich rein gewöhnen, aber wenn du das ganze Konzept einmal verstanden hast, wirst du es mögen |
Wenn du wie ich schon 20 Jahre mit dem klassischen Windows Controls Model (von direkter Verwendung der CommonControls über die VCL zu Winforms) auf dem Buckel hast kann ich dir versichern das es maximal zu Akteptanz reicht und nicht zu mögen  Ich vermute mal OlafSt geht es ähnlich. Gib mir die Renderengine von WPF und MVVM und laß XAML sterben und du siehst mich glücklich.
|
|
Palladin007
      
Beiträge: 1282
Erhaltene Danke: 182
Windows 11 x64 Pro
C# (Visual Studio Preview)
|
Verfasst: Fr 29.08.14 14:53
Naja, jedem das Seine.
Ich mag WPF ^^
Du kannst doch die Renderengine direkt ansprechen, oder?
|
|
OlafSt 
      
Beiträge: 486
Erhaltene Danke: 99
Win7, Win81, Win10
Tokyo, VS2017
|
Verfasst: Fr 29.08.14 15:02
Ralf Jansen hat folgendes geschrieben : | Zitat: | Ja, WPF ist gewöhnungsbedürftig und ja, man muss sich rein gewöhnen, aber wenn du das ganze Konzept einmal verstanden hast, wirst du es mögen |
Wenn du wie ich schon 20 Jahre mit dem klassischen Windows Controls Model (von direkter Verwendung der CommonControls über die VCL zu Winforms) auf dem Buckel hast kann ich dir versichern das es maximal zu Akteptanz reicht und nicht zu mögen Ich vermute mal OlafSt geht es ähnlich. Gib mir die Renderengine von WPF und MVVM und laß XAML sterben und du siehst mich glücklich. |
Dammit, das ist der eigentliche Punkt  Ich habe mit Windows 3.1 angefangen, stecke also bis zum Hals im WinForms-Modell drin, habe dieselbe Karriere wie Ralf Jansen und natürlich ist es schwer, sich an sowas - augenscheinlich - völlig abstruses wie WPF zu gewöhnen. Allerdings sehe ich auch das gewaltige Potential, das WPF in sich birgt. Mein Bruder z.B. ist ein begnadeter PhotoShop-Magier und Delphi-Programmierer. Es ist atemberaubend, was für Oberflächen der Mann zusammenbasteln kann, sie aber mit Delphi und seiner VCL zum Funktionieren zu bringen, treibt ihn oft genug in den Wahnsinn. Wenn WPF eine Möglichkeit bietet, diesen Wahnsinn zu eliminieren oder wenigstens abzumildern, sollten wir dem ganzen ne Chance geben. Ob das dann Liebe wird, wird sich zeigen    Ich hab mir erstmal Literatur besorgt zum Thema WPF, mal schauen, was geht.
Zum Thema Terminate/Abort habe ich was beizusteuern. "Terminate" ist im Englischen das "Beenden" und somit nach meinem Verständnis das saubere Verlassen eines Threads. "Abort" dagegen ist das "Abbrechen", ergo das knallharte "Bullet-into-Head" des Threads. Da liegen unsere Namen also voll im Trend
Ich begreife auch den Sinn hinter diesem Massen-Vermethodieren, nennen wir es einfach mal so  Lesbarkeit geht erstmal vor, so ist die Prämisse - Optimieren kann man später noch, wenn es nötig ist (oder eben mehr CPU-Cores nachstecken xD). Ein Stil, den ich mir angewöhnen sollte, denn die Erfahrung hat gezeigt, das meine Anwendungen eine sehr lange Lebensdauer haben - sprechender Code kann nach 8-10 Jahren Gold wert sein.
MVVM: Das Beispiel mit meinem Bruder hat ja schon angedeutet, das das mit den Oberflächen nicht immer so einfach ist. Und der Gedanke, sich eine UI "photoshoppen" zu lassen, hat schon einen gewissen Reiz. Ich sehe aber auch, das - für mich als Anfänger - noch zu große Lücken klaffen, um das ganze wirklich ernsthaft mal Umsetzen zu wollen. Dazu brauchts wohl erstmal 2 oder 3 Projekte, bevor man sich da mal rantraut.
_________________ Lies, was da steht. Denk dann drüber nach. Dann erst fragen.
|
|
|