Asynchrone Programmierung (mit der TPL) Teil 2
Ja, ich weiß, lang ist es her, dass ich den ersten Teil geschrieben habe aber nun (nach nur 5 Monaten) gehts endlich weiter, und diesmal wirds neben der Theorie auch Code geben!
Wie ihr seht habe ich den Titel etwas abgeändert nun tauchen da drei neue Buchstaben auf die ihr (vielleicht) noch nie gehört habt, diese stehen für Task Parallel Library. Wie der Name es schon sagt, ist es eine Library die es uns ermöglicht parallel zu programmieren, aber dazu später mehr!
Machen wir erstmal mit ein wenig Theorie weiter. Im ersten Teil haben wir erfahren, dass es nicht automatisch heißt, dass ein Programm schneller wird, wenn man viele Prozessoren im Rechner verbaut hat, und diesen Gedanken möchte ich in dem zweiten Teil noch etwas fortführen.
Dazu möchte ich ein Beispiel geben. Nehmen wir an wir haben eine Funktion geschrieben, in der eine FOR-Schleife vorkommt die viele Berechnungen anstellt und die Ergebnisse in ein Array Speichert zb so:
static double [] oldWay = new double[10000000];
public static void doitOldSchool()
{
for (int i = 0; i < oldWay.Length; i++)
{
oldWay[i] = (Math.Sin(i) / Math.Cos(i)) * Math.Tan(i) * 12345;
}
}
Ich denke so eine Schleife hat schon jeder mal geschrieben, da ist nichts kompliziertes dran, sie funktioniert auch hervorragend und liefert uns ein Ergebnis.
Die obere Schleife ist natürlich sehr simpel und ein Durchgang ist schnell erledigt, aber die Schleife wird hier 10 Millionen mal durchlaufen - und das ist ne Menge! Stellt euch vor irgendwann später wird diese Schleife um weitere Rechnungen oder Aktionen erweitert, dann könnte sie die Anwendung dauerhaft blockieren und einfrieren lassen.
Anmerkung: Dies ist nur ein Beispiel, im wahren Leben ist sowas natürlich blöd, da würde die Schleife viel weniger Durchläufe haben, aber dafür einen größeren Body, was die Sache wieder ausgleicht!
Spätestens wenn die Anwendung merkbar langsamer wird lohnt es sich über Parallelisierung nachzudenken, denn wenn inzwischen eh fast jeder Rechner und Laptop eine Mehrkern CPU hat würde das Programm doch schneller laufen oder?
Hier muss man aufpassen, denn die Parallelisierung bietet viele Fallen, denn wenn man die obige Schleife parallelisiert wird sie in sehr viele einzelne Threads zerteilt und auf alle CPU Kerne verteilt. Man kann es sich ungefähr so vorstellen, dass jeder Schleifendurchgang einen Einzelnen Thread bekommt.
Das hört sich eigentlich ganz toll an, aber hier darf man nicht aus den Augen verlieren, dass es auch seine Zeit dauert einen Thread zu erzeugen und dieser erzeugte Thread muss irgendwann auch vernichtet werden. Das heißt also, dass es Zeit kostet einen Thread anzulegen und zu zerstören, und wenn wir uns unsere Schleife so anschauen werden dort ziemlich viele Threads gebildet und auch vernichtet. So kann es vorkommen, dass ein parallel laufendes Programm langsamer ist als sein sequentielles Ebenbild!
Hier gilt aber ein einfache Regel, es muss sich "lohnen" einen Thread zu erzeugen. Das heißt, dass ein Thread auch was zu tun haben muss, in unserem Beispiel ist die Berechnung so schnell, dass das Erzeugen und Vernichten des Threads länger dauert als die Berechnung!
Aber das hängt auch noch von der verwendeten Hardware ab, so werden zu Beispiel Threads auf einem 64-Bit System ca. 30% schneller erzeugt als auf einem 32-Bit System. Desweiteren wird die Performance von der Anzahl der CPU-Kerne beeinflusst, denn wenn davon genug da sind können viel mehr Aktionen gleichzeitig laufen was natürlich das Programm beschleunigt.
Aber gut, genug der Theorie. Nun wollen wir ein bisschen in die Praxis gehen und gucken wie wir mit der Parallelisierung an der Performance der Anwendung schrauben können! Wenn wir die Obige sequentielle Schleife so ausführen benötigt diese knapp 2 Sekunden um alle 10 Millionen Werte zu berechnen:
Bild 1: Das Ergebnis der Sequentiellen Schleife (Core2Duo @ 2,4 gHz)
Gar nicht mal schlecht was man in 2 Sekunden alles berechnen kann! Aber das Blöde hierbei ist, dass die Anwendung dann natürlich 2 Sekunden Blockiert ist. Versuchen wir nun mal die Arbeit gerecht auf alle Prozessoren (in diesem Falle 2) zu verteilen!
Dazu ist aber etwas Vorbereitung nötig. Die TPL wird von Visual Studio 2010 von Haus aus unterstützt, desweiteren wird auch das .NET Framework in der Version 4.0 benötigt, außerdem macht es auch Sinn einen Rechner mit mehr als einem CPU-Kern zu haben
Es müsste auch mit Visual Studio 2008 gehen wenn man das .NET Framework 4 installiert, aber dafür lege ich nicht meine Hand ins Feuer!
Wenn das erledigt ist könnt ihr ein neues C# Konsolen Projekt anlegen. Alles was wir brauchen befindet sich in dem Namespace System.Threading.Tasks, weswegen wir das importieren sollten!
Nun wollen wir unsere FOR-Schleife in eine parallele FOR-Schleife umwandeln. Zum Glück ist das einfacher als es sich anhört, da und die TPL alles nötige zur Verfügung stellt! Schreiben wir also die Schleife folgendermaßen um:
static double [] newWay = new double[10000000];
public static void doitNewSchool()
{
Parallel.For(0, //From (Startwert)
newWay.Length, //TO (Endwert)
(i) => //Initialisierung der Variablen
{ //Code der ausgeführt werden soll
newWay[i] = (Math.Sin(i) / Math.Cos(i)) * Math.Tan(i) * 12345;
});
}
Zugegeben, auf den ersten Blick sieht das ganze verwirrend aus, da der Kopf der Schleife sich geändert hat. Aber auf den zweiten Blick erkennt man viel wieder. So beginnt die Schleife ebenfalls mit dem Startwert, gefolgt von der Abbruchbedingung. Nun kommt das neue, denn die TPL verwendet hier die so genannten Lambda Ausdrücke, die mit C# 3.0 Einzug erhielten. So ist es nun möglich ganze Codeblöcke in Parameter Felder zu setzen und vieles mehr.
Aber man langsam, man beginnt hier mit der Initialisierung der benötigten Variablen in diesem Fall nur eine namens "i", die Initialisierung erfolgt Typenlos in runden Klammern mit dem Pfeil dahinter wird der dazugehörige Codeblock initiiert (Anonyme Methoden). (ihr könnt die Variablen auch Typisieren, wenn ihr aber keinen Typ angibt wird er automatisch bestimmt!)
Der Inhalt der Schleife bleibt hierbei aber exakt der gleiche, praktisch oder? Aber was passiert nun genau? Naja einfach ausgedrückt wird der Block in viele einzelne Teile zerbrochen die in eigenen Threads dann über die verfügbaren CPU-Kerne verteilt werden. Schließlich werden die Ergebnisse wieder zusammengetragen!
Bevor wir nun hingehen und die neue Schleife testen will ich euch noch was zeigen. Auf dem Folgenden Screenshot sieht man die CPU-Auslastung während unsere erste sequentielle Schleife läuft:
Bild 2: CPU-Auslastung während sequentieller Berechnung
Wie man ganz klar erkennen kann werden beide Kerne belastet! Wie kann das sein? Das Programm ist doch sequentiell! Die Erklärung ist relativ simpel, denn das System (hier Windows 7) erkennt auch, dass da ein Programm einen Kern überlastet und verteilt die Last automatisch auf andere Kerne. Ok, und warum soll ich mir nun die mühe machen und parallel programmieren wenn das System das doch auch so für mich macht?
Ganz einfach, weil das System es schlecht macht. Hier kann man es nicht erkennen, da der PC nur 2 Kerne hat. Wenn man mehr Kerne hätte würde man erkennen, dass das System, zwar versucht einen Kern zu entlasten und die Arbeit zu verteilen, aber es bezieht nur 1, vielleicht 2 weitere kerne in die Arbeit mit ein, alle anderen dürfen weiter "karten spielen" und auf Arbeit warten. Wenn wir aber unser Programm parallel gestalten, werden alle verfügbaren Kerne ausgenutzt!
Ok, dann wollen wir mal unsere überarbeitete parallel laufende Schleife mal anschmeißen:
Bild 3: Parallel laufende Schleife
Ahja, wie man sieht konnte unsere neue Schleife die Werte in fast der halben Zeit berechnen. Das ist eine enorme Steigerung! Schauen wir uns auch nochmal die CPU-Auslastung an:
Man erkennt sofort, dass die CPU hier sehr viel effizienter genutzt wird, die Auslastung steigt kurz bis an das Maximum an und geht danach direkt wieder runter! Vergleicht man das Bild mit dem Sequentiellen sieht man auch, dass beide Kerne fast gleichstark und auch durchgängig belastet werden. Die last wird perfekt auf die verschiedenen Kerne verteilt!
Ruft man beide Funktionen hintereinander zur Laufzeit auf, wird der Unterschied noch besser sichtbar:
Der Unterschied ist gewaltig, man würde den Unterschied noch mehr spüren, wenn man eine Schleife mit einem Größerem Body testen würde, aber das könnt ihr dann ja mal selber irgendwann üben.
In bild 5 sieht man die Ergebnisse der Berechnung auf einem Windows 7 32 Bit Rechner mit einem Core2Duo mit 2,4 gHz. Schauen wir uns doch mal an, wie die Zahlen abweichen wenn wir ein System haben dass ein paar mehr Kerne hat und im 64 Bit Modus läuft:
Hier sieht man es nochmal ganz deutlich wie überlegen die parallel programmierte Schleife der sequentiellen ist. So sieht die CPU Verteilung aus auf einem 64 Bit System mit 16 Kernen:
Bild 7: Gegenüberstellung (Zum vergrößern klicken)
Wie man sieht beansprucht die sequentielle Schleife nur 4 der Prozessoren, und das nicht mal ganz. Die parallele Schleife dagegen hat 15 der 16 Kerne belastet und ist dem entsprechend 10 mal schneller fertig geworden!
Zum Schluss will ich noch eine kleine Schwäche der Parallelen Variante (wenn man sie Schwäche nennen mag) zeigen. Ich habe hier ganz bewusst eine Schleife genommen, die 10 Millionen mal ausgeführt wird, denn wenn die Schleife kleiner wird, hat man mit der Parallelen Schleife keinen Vorteil mehr!
Die Erklärung dafür habe ich zu Beginn dieses Artikels gegeben, denn die Thread Erzeugung dauert einfach viel zu lange! So sieht man hier den Ablauf der selben Schleife mit 100.000, 10.000 und 1000 Durchgängen:
Bild 8: Man muss vorher abwägen
Man muss sich unbedingt vorher Gedanken machen, ob es sich lohnt einen Prozess zu parallelisieren.
In dem Nächten Teil, der (ich hoffe) bald kommt werde ich euch dann noch ein paar weitere Gefahren zeigen, und auch Methoden wie man die Parallelisierung noch ein wenig beschleunigen kann, sowie auch weitere Beispiele mit anderen Schleifen und ganzen Codeblöcken!
Ich hoffe der Artikel hat euch gefallen und freue mich auf euer Feedback!
Die Demoanwendung gibts hier:
.
.NET: Taskleiste und Start Button ausblenden
Vor einer Weile habe ich einen Artikel geschrieben, in dem ich zeige wie man die Windows Taskleiste ausblenden kann, sodass nur der Startbutton übrig bleibt. Nun wollen wir auch noch den Startbutton entfernen, sodass von der Taskleiste nichts mehr übrig bleibt!
Das Vorgehen ist hierbei fast das gleiche, der Unterschied besteht nur darin, dass der Prozess, der für die Anzeige des Start-Buttons verwendet wird etwas schwerer zu finden ist! Diesen Prozess werdet ihr vergeblich in der Process.getProcesses() Übersicht des .NET Frameworks suchen.
Hier müssen wir so vorgehen wie in dem Beitrag "Alle Sichtbaren Prozesse auflisten" und benutzen deshalb die Windows Funktion "EnumWindows" die uns alle Fensterprozesse liefert, inklusive dem Start-Button Prozess.
Suchen müssen wir hier den Prozess mit dem Titel "Start" und von der Klasse "Button". Ich habe hier um die Arbeit zu erleichtern 2 kleine Klassen geschrieben. (Ich werde bald eine Library veröffentlichen mit weiteren Funktionen)
ProcessListDemo.Windows windows = new ProcessListDemo.Windows();
foreach (ProcessListDemo.Window w in windows.lstWindows)
{
if (w.winTitle == "Start" &amp;&amp; w.winClass == "Button" &amp;&amp; w.winVisible == true)
{
sbHandle = w.winHandle;
if (sbHandle != IntPtr.Zero)
{
ShowWindow(sbHandle, 0);
}
else
{
//Fehler
}
}
}
Wenn man nun das Handle des Startbuttons hat kann man mit der Windows Funktion ShowWindow das Fenster bzw. den Button ausblenden.
Bild 1: Demoanwendung kann nun auch den Startbutton verschwinden lassen
Wundert euch nicht, dass der Button nicht verschwindet wenn ihr den Code ausführt, denn erst wenn auch die Taskleiste verschwunden ist ist auch der Button weg. Also kann man nur den Button anzeigen oder Taskleiste und Button. Nur die Taskleiste (also ohne den Start Knopf) Geht leider nicht!
Probiert es einfach mit der Demoanwendung aus:
.
.
VB.NET: Netzwerkadapter Einstellungen auslesen
Das .NET Framework bietet viele sehr nützliche Schnittstellen, so auch um Netzwerkinformationen auszulesen. Alles was man braucht findet man in dem System.Net Namensraum. In dieser Demo möchte ich euch zeigen, wie man alle verfügbaren Netzwerkadapter auflistet und deren Informationen ausließt.
So lässt sich sehr einfach eine Auflistung aller Netzwerkschnittstellen des Systems zusammenstellen:
Dim adapter As NetworkInterface 'Eine Netzwerkadapter Instanz
Dim adapters As NetworkInterface() 'Array mit allen Netzwerkadaptern
Public Sub DisplayDnsConfiguration()
adapters = NetworkInterface.GetAllNetworkInterfaces()
'Alle Adapter in der Liste durchlaufen
For Each Me.adapter In adapters
Dim properties As IPInterfaceProperties = adapter.GetIPProperties()
lstNetworks.Items.Add(adapter.Description)
Next adapter
End Sub
Hier wird die lstNetworks mit allen verfügbaren Netzwerkadaptern gefüllt. Da wir nun die Bezeichnungen der Netzwerkadapter haben können wir uns nun daran machen deren Einstellungen auszulesen.
Die Einstellungen sind hierbei genauso schnell ausgelesen, denn wenn man erst die richtige NetworkInterface-Instanz erwischt hat kann man ganz bequem auf die Einstellungen zugreifen:
Bild 1: Ein paar Eigenschaften des Netzwerkadapters
Den dazugehörigen Code spare ich mir mal an dieser Stelle, da es einfach zu viel Text ist. Ihr könnt diesen aber natürlich in dem Demoprojekt nachgucken.
Wie in Bild 1 auch zu erkennen ist hat man auch Zugriff auf die übertragenen Bytes in beide Richtungen. So kann man zB. auch sehr einfach die aktuelle Netzwerkauslastung bestimmen, indem man den alten Wert speichert und mit dem neuen Wert nach einer Sekunde vergleicht. Dadurch bekommt man die übertragenen Bytes pro Sekunde. (Dies könnt ihr auch in dem Demoprojekt nachgucken)
Bild 2: Das Demoprogramm in Aktion (klicken zum Vergrößern)
In dem Demoprogramm habe ich mal versucht die am häufigsten benötigten Informationen einer Netzwerkschnittstelle zusammenzufassen, aber das meiste ist hier aber auch nur Copy’n’Paste!
Das Demoprogramm zeigt alle verfügbaren netzwerkschnittstellen, deren Eigenschaften sowie die aktuelle Adapterauslastung in KB/s.
101 LINQ Beispiele fuer C# Programmierer
Wer Datenbankprogrammierung mit C# betreibt wird früher oder später nicht an LINQ vorbeikommen. Wer sich damit etwas auseinandersetzt wird schnell merken, dass diese Queries schnell recht komplex werden.
Hier bietet Mocrosofts MSDN eine tolle Beispielsammlung an, in der fast alle Anwendungsfälle beschrieben sind. Das ganze findet ihr im MSDN Visual C# Developer Center.
.NET: Projekte fuer Mobile Geraete koennen auf dem Desktop ausgefuehrt werden
Ich denke, dass viele von euch es schon längst gewusst haben, aber heute habe ich zufällig entdeckt, dass man die Projekte, die für Smartphones mit Windows Mobile programmiert wurden direkt auf dem Desktop ausführen kann.
Dabei bedarf es keinerlei Anpassung oder Modifikation des Codes. Einfach doppelklicken und ab geht's!
![]()
Bild 1: Das selbe Programm auf beiden Plattformen (Bild von Tom Wendel)
Das ist (so weit ich weiß) einzigartig in der Programmierwelt oder? Denn Java Programme können nicht direkt auf den Desktop ausgeführt werden, und genauso iPhone Apps!
Ich bin mal gespannt, wie lange es dauert bis das Ganze auch umgekehrt möglich ist!
Wirklich genial!





