Günther's profileGünther Foidl (gfoidl)PhotosBlogGuestbookMore ![]() | Help |
|
|
July 28 TreeView Drag & DropUm in einem TreeView eine Drag & Drop – Operation ähnlich jener im Explorer von Windows durchzuführen wird nur ein bischen Code benötigt – wer hätte das Gedacht ;) 1: using System; 2: using System.Drawing; 3: using System.Windows.Forms; 4: 5: namespace TreeView_DragDrop 6: {7: public partial class Form1 : Form 8: {9: private bool _dragdropCopy = false; 10: private DragEventArgs _dragEventArgs; 11: private MouseButtons _mouseButton; 12: //--------------------------------------------------------------------- 13: public Form1() 14: { 15: InitializeComponent(); 16: }17: //--------------------------------------------------------------------- 18: /// <summary> 19: /// Beginnt den DragDrop-Vorgang 20: /// </summary> 21: private void treeView1_ItemDrag(object sender, ItemDragEventArgs e) 22: {23: // Zu verschiebenden Knoten wählen: 24: TreeNode sourceNode = (TreeNode)e.Item; 25: 26: _mouseButton = e.Button; 27: 28: // DragDrop beginnen: 29: DoDragDrop(sourceNode, DragDropEffects.Move | DragDropEffects.Copy); 30: }31: //--------------------------------------------------------------------- 32: /// <summary> 33: /// Validiert ob der DragDrop-Vorgang zugelassen wird 34: /// </summary> 35: private void treeView1_DragEnter(object sender, DragEventArgs e) 36: {37: // Nur TreeNodes werden zugelassen: 38: if (e.Data.GetDataPresent(typeof(TreeNode))) 39: {40: // Strg-Taste gedrück? Prüfung über Bitmaske: 41: if ((e.KeyState & 8) == 8) 42: e.Effect = DragDropEffects.Copy;43: // nur linke Maustaste 44: else 45: e.Effect = DragDropEffects.Move; 46: }47: else 48: e.Effect = DragDropEffects.None; 49: }50: //--------------------------------------------------------------------- 51: /// <summary> 52: /// Verschiebt den Knoten 53: /// </summary> 54: private void treeView1_DragDrop(object sender, DragEventArgs e) 55: { 56: _dragEventArgs = e; 57: 58: // Behandlung von Verschieben oder Kopieren: 59: if (_mouseButton == MouseButtons.Right) 60: { 61: ctxDragDropKopieren.Visible = 62: ((e.AllowedEffect & DragDropEffects.Copy) == DragDropEffects.Copy); 63: ctxDragDropVerschieben.Visible = 64: ((e.AllowedEffect & DragDropEffects.Move) == DragDropEffects.Move); 65: ctxDragDrop.Show(e.X, e.Y); 66: }67: else 68: { 69: _dragdropCopy = (e.Effect == DragDropEffects.Copy); 70: TreeViewDragDropVerarbeiten(); 71: } 72: }73: //--------------------------------------------------------------------- 74: private void ctxDragDropKopieren_Click(object sender, EventArgs e) 75: {76: _dragdropCopy = true; 77: TreeViewDragDropVerarbeiten(); 78: }79: //--------------------------------------------------------------------- 80: private void ctxDragDropVerschieben_Click(object sender, EventArgs e) 81: {82: _dragdropCopy = false; 83: TreeViewDragDropVerarbeiten(); 84: }85: //--------------------------------------------------------------------- 86: private void TreeViewDragDropVerarbeiten() 87: {88: TreeNode sourceNode = _dragEventArgs.Data.GetData(typeof(TreeNode)) as TreeNode; 89: 90: // Knoten ermitteln dem der gedragte Knoten hinzugefügt werden 91: // soll: 92: Point p = treeView1.PointToClient(new Point( 93: _dragEventArgs.X, 94: _dragEventArgs.Y)); 95: TreeNode targetNode = treeView1.GetNodeAt(p); 96: 97: if (targetNode != null) 98: { 99: TreeNode newNode = (TreeNode)sourceNode.Clone(); 100: 101: // Hinzufügen des neuen Knotens: 102: targetNode.Nodes.Add(newNode); 103: 104: // Verschieben oder Kopieren? 105: if (!_dragdropCopy) // Verschieben 106: sourceNode.Remove(); 107: 108: // Neuzeichnen des TreeView: 109: treeView1.Invalidate(); 110: treeView1.ExpandAll(); 111: } 112: } 113: } 114: }Das Projekt ist angehängt in den Versionen für C# und VB.net. Happy coding ;) July 14 Anwendungseinstellungen für eigene TypenAnnahme: Es sollen Daten per Benutzerbasis in einer Konfigurationsdatei gespeichert werden. Sollte soweit bekannt sein. Was ist aber wenn keine einfache Datentypen gespeichert werden sollen wie zB eine Liste aus eigenen Objekten? Visual Studio bietet dafür keine Unterstützung. Möglichkeiten:
Die erste Möglichkeit liegt auf der Hand hat aber den Nachteil dass die Möglichkeit die Daten per User zu speichern nicht mehr so einfach möglich ist. Einzige Bedingung für die zweite Möglichkeit ist dass die zu speichernden Daten als XML serialisierbar sind. Die Klasse die gespeichert werden soll1: using System; 2: using System.Xml; 3: using System.Xml.Serialization; 4: 5: namespace UserSettings_mit_Listen 6: { 7: [Serializable]8: public class MyListItem 9: { 10: [XmlAttribute]11: public Guid ID { get; set; } 12: 13: public string Name { get; set; } 14: //--------------------------------------------------------------------- 15: public MyListItem() 16: {17: this.ID = Guid.NewGuid(); 18: } 19: } 20: }Erweiterung der AnwendungseinstellungenDie von Visual Studio erzeugte Settings-Klasse ist mit dem partial-Modifizierer dekoriert und somit ist es möglich diese Klasse zu erweitern. Dies ist ganz einfach: 1: using System.Collections.Generic; 2: using System.Configuration; 3: 4: namespace UserSettings_mit_Listen.Properties 5: {6: /// <summary> 7: /// Erweitert die vom Designer generierten Settings so dass 8: /// eine Liste gespeichert werden kann. 9: /// </summary> 10: partial class Settings 11: { 12: [UserScopedSetting()] 13: [SettingsSerializeAs(SettingsSerializeAs.Xml)]14: [DefaultSettingValue("")] 15: public List<MyListItem> MyList 16: {17: get { return this["MyList"] as List<MyListItem>; } 18: set { this["MyList"] = value; } 19: } 20: } 21: }Das war schon der ganze “Zauber”. Es ist nun möglich auf diese Eigenschaft der Settings-Klasse wie gewohnt zuzugreifen. Happy coding ;) July 05 Entwurfsmuster – Teil 2.3 (Praxisbeispiel)Fortsetzung von Entwurfmuster – Teil 1, Teil 2.1, Teil 2.2, Teil 2.3 Angenommen es geht darum in einer Datenbank alle Tabellen auf Korrektheit (es wird hier nicht geäußert was Korrektheit ist) zu prüfen. Das bestehende OR-Mapping soll dabei so wenig wie möglich geändert werden. Möglich wäre dies durch Erweiterungsmethoden. Dabei empfinde ich jedoch nachteilig dass mit if-Ausdrücken der Type der Tabelle geprüft werden muss und dann im if-Block der Code zur Prüfung steht bzw. von dort aus die Methode zur Prüfung aufgerufen wird. Dies führt zu unübersichtlichen Code. Durch Verwendung des klassischen Besucher-Musters muss das OR-Mapping einmalig angepasst werden um Besucher zu akzeptieren. Auf Besucherseite kann dann der Code sauber getrennt implementiert werden. Dies ist aber auch nicht ideal denn die bestehenden Klassen müssen angepasst werden. Ideal ist somit eine Kombination von Erweiterungsmethoden und dem klassischen Besucher-Muster. Am vorigen Beispiel mit der Reise soll dies nun demonstriert werden. Das Hauptprogramm und der Code für den Besucher können gleich bleiben. Die Klassen für die Reise gehen wieder zurück in den ursprünglichen Zustand wie in Teil 2.1 dargestellt. Der neue Code schaut wie folgt aus. 1: using System; 2: using System.Collections.Generic; 3: 4: namespace gfoidl.Test.Visitor 5: {6: public abstract class Land { } 7: //------------------------------------------------------------------------- 8: /// <summary> 9: /// Die Reise stellt die Objekthierarchie dar. 10: /// </summary> 11: public class Reise : List<Land> { } 12: //------------------------------------------------------------------------- 13: /// <summary> 14: /// Das Land Österreich ist ein Teil der Länder die besucht werden sollen. 15: /// </summary> 16: public class Österreich : Land { } 17: //------------------------------------------------------------------------- 18: /// <summary> 19: /// Auch Kuba ist ein Teil der Länder die besucht werden sollen. 20: /// </summary> 21: public class Kuba : Land { } 22: //------------------------------------------------------------------------- 23: public static class LandExtension 24: {25: public static void Accept(this Reise reise, ILandVisitor visitor) 26: {27: foreach (Land land in reise) 28: {29: if (land is Österreich) 30: (land as Österreich).Accept(visitor); 31: else if (land is Kuba) 32: (land as Kuba).Accept(visitor); 33: } 34: }35: //--------------------------------------------------------------------- 36: public static void Accept(this Österreich österreich, ILandVisitor visitor) 37: { 38: visitor.Visit(österreich); 39: }40: //--------------------------------------------------------------------- 41: public static void Accept(this Kuba kuba, ILandVisitor visitor) 42: {43: if (visitor is VisitorFromUSA) 44: throw new NotSupportedException("USA darf nicht einreisen."); 45: else 46: visitor.Visit(kuba); 47: } 48: } 49: }Die Funktionalität des Akzeptieren von Besuchern wurde durch die Erweiterungsmethoden bereitgestellt. Es wäre auch möglich anstatt der if-else-Abfrage in der foreach-Schleife die passende Methode durch Reflektion zu bestimmen. Dazu muss ein wenig in die “Trickkiste” gegriffen werden. Somit ändert sich der Rumpf der Schleife zu 1: foreach (Land land in reise) 2: {3: // Eine Erweiterungsmethode ist nichts anderes als eine 4: // statische Methode! 5: 6: Type t = typeof(LandExtension); 7: 8: // Da mehrere Methoden mit den selben BindingFlags 9: // existieren wird nach der Signatur gesucht: 10: MethodInfo mi = t.GetMethod(11: "Accept", 12: new Type[] { land.GetType(), typeof(ILandVisitor) }); 13: 14: // Methode ausführen. Statische Methoden werden auf keine 15: // Objekt ausgeführt -> daher null: 16: mi.Invoke(null, new object[] { land, visitor }); 17: }und das Beispiel ist “perfekt”. Happy coding ;) Entwurfsmuster – Teil 2.2Fortsetzung von Entwurfmuster – Teil 1, Teil 2.1, Teil 2.2, Teil 2.3 Besucher-Muster (klassische Umsetzung)Die Defintion der Reise: 9: public interface IVisitorElement 10: {11: void Accept(ILandVisitor visitor); 12: }13: //------------------------------------------------------------------------- 14: /// <summary> 15: /// Die Reise stellt die Objekthierarchie dar. 16: /// </summary> 17: public class Reise : List<IVisitorElement> 18: {19: /// <summary> 20: /// Den Besucher in jedes Land führen das Teil der Reise ist. 21: /// </summary> 23: public void Accept(ILandVisitor visitor) 24: {25: foreach (IVisitorElement element in this) 26: element.Accept(visitor); 27: } 28: }29: //------------------------------------------------------------------------- 30: /// <summary> 31: /// Das Land Österreich ist ein Teil der Länder die besucht werden sollen. 32: /// </summary> 33: public class Österreich : IVisitorElement 34: {35: /// <summary> 36: /// Akzeptiert einen Besucher und dreht den Spieß um. D.h. dem Besucher wird mitgeteilt er ist jetzt in Österreich. 38: /// </summary> 40: public void Accept(ILandVisitor visitor) 41: {42: visitor.Visit(this); 43: } 44: }45: //------------------------------------------------------------------------- 46: /// <summary> 47: /// Auch Kuba ist ein Teil der Länder die besucht werden sollen. 48: /// </summary> 49: public class Kuba : IVisitorElement 50: {51: /// <summary> 52: /// Akzeptiert einen Besucher und dreht den Spieß um. D.h. dem Besucher wird mitgeteilt er ist jetzt in Kuba. 54: /// Es sei denn der Besucher ist USA-Bürger dann wird der Besuch verwährt. 55: /// </summary> 57: public void Accept(ILandVisitor visitor) 58: {59: if (!(visitor is VisitorFromUSA)) 60: visitor.Visit(this); 61: else 62: throw new NotSupportedException("USA darf nicht einreisen."); 63: } 64: }Darin akzeptiert jedes Land einen Besucher und teilt dem Besucher mit wo er ist. Der Spieß wird also umgedreht. Nicht der Besucher führt die Aktion je nach Typ des Landes aus sondern das Land teilt dem Besucher mit welche Methode (Operation) er ausführen soll bzw. im Beispiel was er konkret besuchen soll. 5: public interface ILandVisitor 6: {7: void Visit(Österreich Österreich); 8: void Visit(Kuba kuba); 9: }10: //------------------------------------------------------------------------- 11: /// <summary> 12: /// Ein Besucher der die Hauptstädte besucht. 13: /// </summary> 14: public class HauptstädteVisitor : ILandVisitor 15: {16: /// <summary> 17: /// Besucht die Hauptstadt von Österreich. 18: /// </summary> 19: public void Visit(Österreich Österreich) 20: {21: Console.WriteLine("Der Besucher {0} besucht Wien.", this.GetType().Name); 24: }25: //--------------------------------------------------------------------- 26: /// <summary> 27: /// Besucht die Hauptstadt von Kuba. 28: /// </summary> 29: public void Visit(Kuba kuba) 30: {31: Console.WriteLine("Der Besucher {0} besucht Havanna.", this.GetType().Name); 34: } 35: }36: //------------------------------------------------------------------------- 37: /// <summary> 38: /// Ein Besucher der den schönsten Platz des Landes besucht. 39: /// </summary> 40: public class SchönsterPlätzVisitor : ILandVisitor 41: {45: public void Visit(Österreich Österreich) 46: {47: Console.WriteLine("Der Besucher {0} besucht Waidring / Tirol.", this.GetType().Name); 50: }51: //--------------------------------------------------------------------- 55: public void Visit(Kuba kuba) 56: {57: Console.WriteLine("Der Besucher {0} besucht Varadero.", this.GetType().Name); 60: } 61: }62: //------------------------------------------------------------------------- 66: public class VisitorFromUSA : HauptstädteVisitor { } Es wird also für jeden Typ (jedes Land) eine entsprechende Method deklariert die vom besuchten Land aus aufgerufen wird (durch visitor.Visit(this);) 7: static void Main(string[] args) 8: {9: // Die Reise erstellen. 10: Reise reise = new Reise(); 11: reise.Add(new Österreich()); 12: reise.Add(new Kuba()); 13: 14: // Einen Besucher durch die Hauptstädte der Länder in der Reise schicken. 16: ILandVisitor visitor = new HauptstädteVisitor(); 17: reise.Accept(visitor);20: // Einen (anderen) Besucher durch die schönsten Plätze der Länder der Reise schicken. 22: visitor = new SchönsterPlätzVisitor(); 23: reise.Accept(visitor);26: // Einen USA-Bürger besucher durch die Hauptstädte schicken: 27: visitor = new VisitorFromUSA(); 28: try { reise.Accept(visitor); } 32: catch (Exception ex) { Console.WriteLine(ex.Message); } 38: }Entwurfsmuster – Teil 2.1Fortsetzung von Entwurfmuster – Teil 1, Teil 2.1, Teil 2.2, Teil 2.3 Besucher (Visitor)Laut Definition ist das Besucher-Muster:
Aha – das sind also Erweiterungsmethoden (Extension methods) die es in C# gibt. Diese entsprechen genau der obigen Definition. Vermutlich haben viele schon mit den Erweiterungsmethoden gearbeitet (zB bei Verwendung von LINQ) oder sogar selbst welche definiert ohne zu wissen dass sie das Besucher-Muster verwenden. Ist es dann überhaupt sinnvoll einen Artikel über dieses Muster zu verfassen wenn es mit Erweiterungsmethoden so einfach zu implementieren ist? Diese Frage hab ich mir auch gestellt – Haha ;) Als Antwort ist herausgekommen: Ja. Sonst wärst du als Leser gar nie zu dieser Zeile gekommen ;) Das Besucher-Muster hat für mich den interessanten Aspekt dass der “Spieß umgedreht wird”. BeispielDazu ein Beispiel indem auch die Bezeichnungen klar werden sollen. Variante mit ErweiterungsmethodenZunächst mal die Klassen welche die Länder der Reise darstellen: 1: using System; 2: using System.Collections.Generic; 3: 4: namespace ConsoleApplication1 5: {6: /// <summary> 7: /// Die Basisklasse für ein Land. 8: /// </summary> 9: public abstract class Land { } 10: //------------------------------------------------------------------------- 11: /// <summary> 12: /// Das Land Österreich ist ein Teil der Länder die besucht werden sollen. 13: /// </summary> 14: public class Österreich : Land { } 15: //------------------------------------------------------------------------- 16: /// <summary> 17: /// Auch Kuba ist ein Teil der Länder die besucht werden sollen. 18: /// </summary> 19: public class Kuba : Land { } 20: }Dann gibt es für jede Art der Reise eine Erweiterungsmethode: 1: /// <summary> 2: /// Erweiterung des Landes. 3: /// </summary> 4: public static class LandExtension 5: {6: /// <summary> 7: /// Zeigt einem Besucher die Hauptstadt. 8: /// </summary> 9: /// <param name="land"></param> 10: public static void BesucheHauptstadt(this Land land) 11: {12: if (land is Österreich) 13: Console.WriteLine("Besuche Wien."); 14: else if (land is Kuba) 15: Console.WriteLine("Besuche Havanna."); 16: }17: //--------------------------------------------------------------------- 18: /// <summary> 19: /// Zeigt einem Besucher den schönsten Platz. 20: /// </summary> 21: /// <param name="land"></param> 22: public static void BesucheSchönstenPlatz(this Land land) 23: {24: if (land is Österreich) 25: Console.WriteLine("Besuche Waidring / Tirol."); 26: else if (land is Kuba) 27: Console.WriteLine("Besuche Varadero."); 28: } 29: }Und das Hauptprogramm: 1: static void Main(string[] args) 2: {3: // Die Reise erstellen: 4: List<Land> reise = new List<Land>(); 5: reise.Add(new Österreich()); 6: reise.Add(new Kuba()); 7: 8: // Einen Besucher durch die Hauptstädte der Länder in der Reise 9: // schicken: 10: foreach (Land land in reise) 11: land.BesucheHauptstadt(); 12: Console.WriteLine(); 13: 14: // Einen (anderen) Besucher durch die schönsten Plätze der 15: // Länder der Reise schicken: 16: foreach (Land land in reise) 17: land.BesucheSchönstenPlatz(); 18: 19: Console.ReadKey(); 20: }Es ist ersichtlich dass es relativ einfach ist dies umzusetzen. Aber was ist mit dem Besucher der USA? Der dürfte doch gar nicht nach Kuba. Stimmt, aber dies hier wurde hier nicht umgesetzt und könnte durch ein zusätzliches Argument bewerkstelligt werden. Nachteilig an dieser Variante ist dass in den Erweiterungsmethoden geprüft werden muss welches konkrete Land vorhanden ist. Kommt ein neues Land zur Reise hinzu muss jede Erweiterungsmethode angepasst werden. Durch Verwendung des Besucher-Musters kann dies anders umgesetzt werden. Siehe nächsten Artikel. Entwurfsmuster (Teil 1)Einleitende WortDie meisten Probleme wurden bereits einmal gelöst. Dabei wurde ein bestimmtes Muster für die Lösung des Problems verwendet. Die GoF hat ein Buch über Entwurfsmuster herausgegeben (das ich nicht gelesen habe ;) indem Muster für häufige Probleme zusammengefasst sind. Dies sind Muster die sich im Einsatz bewährt haben und unabhängig von jeder Programmiersprache definiert sind. Meiner Meinung nach macht es keinen Sinn sich sehr streng an die Definition dieser Muster zu halten denn mit C# haben wir eine sehr hoch entwickelte Programmiersprache die Möglichkeiten bietet von denen die GoF noch gar nichts wusste ;). An dieser muss ich dem Entwicklerteam rund um Anders Hejlsberg danken. Ich meine somit wenn wir unser “Werkzeug” kennen und wissen welche Möglichkeiten geboten sind lässt sich die Lösung der Probleme oftmals mit den C#-Boardmitteln (eleganter) lösen. Ich verwende für meine Codes meine “eigenen” Muster – glaube das macht jeder so – ohne zu wissen dass die GoF dieses Muster mit einem Namen versehen hat und eine wissenschaftliche Abhandlung darüber schrieb. Beispielsweise ist es für mich ganz normal Polymorphismus zu verwenden. Ich hatte bis vor kurzem keine Ahnung dass dies unterm dem Namen “Strategie” als Entwurfsmuster bezeichnet werden könnte. Kann mir auch egal sein denn für mich steht der Nutzen im Vordergrund und ich lege nicht allzu viel Wert darauf wie etwas bezeichnet wird bzw.. ob die Kategorisierung exakt diesem oder jenem entspricht. Man könnte jetzt meinen ich bin ein Gegner der GoF-Muster. Nicht ganz: Die Muster sind gut und bieten einige Denkanstöße. Ich bin ein Gegner davon dass logisches, kreatives Denken (kann man das als Intelligenz bezeichnen?) auf eine Reduktion des Auffindens eines passenden Musters stattfindet. Am schlimmsten ist dann noch wenn jemand ganz erpicht darauf ist die Bezeichnung der Muster-Definition möglichst exakt einzuhalten. Es gibt einige Muster die sich sehr ähneln. Mir ist es dann egal ob ich das Muster A oder das Muster B verwende. Mir geht es um die Abstraktion der Idee hinter dem Muster um somit davon zu lernen. Wenn ich verstanden habe wie ein Muster funktioniert kann ich das in meinen eigenen Worten – bzw.. Code wiedergeben. Dabei spielt es wirklich keine Rolle ob ich nun exakt das Muster A oder Muster B handelt. Nun könnte man argumentieren wenn man sich an die Bezeichnungen der Definition hält ist es sofort ersichtlich “Was” im Code passiert. Das stimmt, aber wozu gibt es Kommentare? Die zu lösenden Probleme sind in der Realität selten so einfach wie die Beispielsimplementierungen der Entwurfsmuster. Also sind Kommentare sowieso notwendig und in diesen kann/soll auch beschrieben werden was im Code bzw.. dem folgenden Code-Abschnitt passiert. Nichts desto trotz zeige ich im nächsten Artikel was das Besucher-Muster (Visitor pattern) ist. Dies deshalb weil ich etwas ähnliches brauchte und per Zufall auf http://www.dofactory.com/Patterns/Patterns.aspx gestossen bin. Mehr dazu aber im nächstes Teil. Gruss July 04 Parallelisierung (Teil 2)Nachdem wir eine For-Schleife parallelisiert haben will ich zeigen wie eine parallel Filterung einer Sequenz nach einem Prädikat – kurz: Where von Linq – parallelisiert werden kann. Die WhereParallel-Methode wird wie das Linq-Pendant ebenfalls als Erweiterungsmethode implementiert. Die Vorgehensweise ist ähnlich jenem der For-Schleife. Die Arbeit wird in Teilaufgaben gebrochen – den Chunks – und diese werden parallel abgearbeitet. Da hier ein Ergebnis zurückgegeben wird werden die Teilergebnisse kombiniert. Dies ist problemlos möglich wenn die Lösung des Problems linear kombiniert werden kann ;) Diese Version erwies sich bei meines Tests ebenfalls schneller als die AsParallel()-Erweiterung der Parallel Extensions. Nun zum Code: 1: using System; 2: using System.Collections.Generic; 3: using System.Linq; 4: using System.Threading; 5: 6: namespace gfoidl.Parallelization 7: {8: /// <summary> 9: /// Erweiterungsmethoden für IEnumerable(T). 10: /// </summary> 11: public static class IEnumerableExtensions 12: {13: /// <summary> 14: /// Parallel Implementierung einer Filterung einer Sequenz 15: /// basierend auf einem Prädikat. 16: /// </summary> 17: /// <typeparam name="T">Der Typ der Enumeration.</typeparam> 18: /// <param name="source">Die Quellliste.</param> 19: /// <param name="predicate">Die Filterbedingung.</param> 20: /// <returns>Gefilterte Enumeration.</returns> 21: public static List<T> WhereParallel<T>( 22: this IEnumerable<T> source, 23: Func<T, bool> predicate) 24: {25: // Anzahl der Elemente: 26: int N = source.Count(); 27: 28: // Typischerweise 2x die Anzahl der Prozessoren für die 29: // Lastverteilung: 30: int P = 2 * Environment.ProcessorCount; 31: 32: // Größe des Arbeits-Chunk: 33: int chunk = N / P; 34: 35: // Zähler für die Arbeitschunks: 36: int counter = P; // Anfangswert. 37: 38: AutoResetEvent signal = new AutoResetEvent(false); 39: object locker = new object(); 40: 41: // Teilmengen erzeugen: 42: T[][] parts = new T[P][]; 43: for (int i = 0; i < P; i++) 44: {45: // Größe des Chunks: 46: int n = i + 1 == P ? N : (i + 1) * chunk; 47: 48: parts[i] = source.Where( 49: (item, idx) => 50: i * chunk <= idx && 51: idx < n) 52: .ToArray(); 53: } 54: 55: List<T> result = new List<T>(); 56: ParallelLoops.For(0, P, i => 57: {58: foreach (T item in parts[i]) 59: if (predicate(item)) 60: lock (locker) 61: { 62: result.Add(item); 63: } 64: 65: if (Interlocked.Decrement(ref counter) == 0) 66: signal.Set(); 67: }); 68: 69: signal.WaitOne(); 70: 71: return result; 72: } 73: } 74: }Parallelisierung (Teil 1)VorwortNachdem mit .net 4.0 die parallele Verarbeitung für jedermann möglich ist will ich einige meiner “Schätze” die ich mir für die Parallelisierung in der Zeit vor den Parallel Extension erstellt habe. Das interessante daran wird wohl eher das Verständnis von parallelen Vorgängen sein während die praktische Verwendung eher auf den .net-Bordmitteln beruhen wird. Wie gesagt will ich also ein paar Methoden/Klassen veröffentlichen. Den Beginn mach dabei eine klassische For-Schleife die parallelisiert wird. For-SchleifeNeben dem leistungsmäßigen Vorteil gegenüber der For-Schleife in den Parallel-Extensions (wahrscheinlich aufgrund der Einfachheit) bietet diese Variante ein praktische Möglichkeit: Fehler werden weitergeleitet! Der Code ist gut kommentiert und sollte selbst erklärend sein: 1: using System; 2: using System.Collections.Generic; 3: using System.Text; 4: using System.Threading; 5: 6: namespace gfoidl.Parallelization 7: {8: /// <summary> 9: /// Statische Klasse die Methoden zur parallelen Verarbeitung bietet. 10: /// </summary> 11: public static class ParallelLoops 12: {13: /// <summary> 14: /// Parallele For-Schleife. 15: /// </summary> 16: /// <param name="start"> 17: /// Startindex (inklusiv) 18: /// </param> 19: /// <param name="end"> 20: /// Endindex (exklusiv) 21: /// </param> 22: /// <param name="action"> 23: /// Die Operation die ausgeführt werden soll. 24: /// </param> 25: /// <exception cref="Exception"> 26: /// Tritt auf wenn in der Operation ein Fehler aufgetreten ist. 27: /// </exception> 28: /// <exception cref="ArgumentException"> 29: /// Tritt auf wenn 30: /// <list type="bullet"> 31: /// <item> 32: /// Der Startindex darf nicht größer als der Endindex sein. 33: /// </item> 34: /// <item> 35: /// Es ist keine Operation übergeben worden. 36: /// </item> 37: /// </list> 38: /// </exception> 39: public static void For(int start, int end, Action<int> action) 40: {41: // Prüfungen: 42: if (start > end) 43: throw new ArgumentException( 44: "Der Startindex darf nicht größer als der Endindex sein."); 45: 46: if (action == null) 47: throw new ArgumentException( 48: "Es ist keine Operation übergeben worden."); 49: 50: // Größe der For-Schleife: 51: int N = end - start; 52: 53: // Typischerweise 2x die Anzahl der Prozessoren für die 54: // Lastverteilung: 55: int P = 2 * Environment.ProcessorCount; 56: 57: // Größe des Arbeits-Chunk: 58: int chunk = N / P; 59: 60: // Zähler für die Arbeitschunks: 61: int counter = P; // Anfangswert. 62: 63: // Fürs Warten bis die Schleifen fertig sind: 64: AutoResetEvent signal = new AutoResetEvent(false); 65: 66: // Für Ausnahmen: 67: object locker = new object(); 68: List<Exception> exceptions = new List<Exception>(); 69: 70: // Arbeitschunks verteilen: 71: for (int i = 0; i < P; i++) 72: { 73: ThreadPool.QueueUserWorkItem(o => 74: {75: // Aktueller "Prozessor": 76: int unit = (int)o; 77: 78: // Über diesen Arbeits-Chunk iterieren in 79: // Abhängigkeit der Obergrenze vom Chunk: 80: int n = (unit + 1 == P ? N : (unit + 1) * chunk); 81: for (int j = unit * chunk; j < n; j++) 82: {83: try 84: {85: // Arbeit durchführen: 86: action(j); 87: }88: catch (Exception ex) 89: {90: // Threadsicherheit! 91: lock (locker) 92: { 93: exceptions.Add(ex); 94: } 95: } 96: } 97: 98: // Zähler des Arbeitschunk dekrementieren: 99: if (Interlocked.Decrement(ref counter) == 0) 100: signal.Set(); 101: }, i); 102: } 103: 104: // Warten bis Thread-Operationen fertig sind: 105: signal.WaitOne(); 106: 107: // Auf Fehler prüfen: 108: if (exceptions.Count > 0) 109: {110: StringBuilder sb = new StringBuilder(exceptions.Count); 111: exceptions.ForEach(e =>112: sb.AppendFormat("{0}\n\n", e.ToString())); 113: 114: throw new Exception( 115: "Fehler in der Operation von Parallel.For\n\n" + 116: sb.ToString()); 117: } 118: } 119: } 120: }July 03 Manchmal lohnt sich das Warten“Zum Warten muss man Zeit haben” ist ein sehr tiefgründer Spruch eines Stammtisch-Kollegen von mir. Ich schreibe diesen Eintrag weil ich kundtun will dass sich das Warten manchmal auszahlt. Recht lange programmiere ich nun ja noch nicht mit .net (C#) oder im Allgemeinem mit einer OOP-Sprache – ca. seit Mai/Juni 2008, zuvor Fortran 95. Ich konnte es also erwarten bis mit .net 3.5 einige sehr hilfreiche Features vorhanden sind mit denen ich Aufgaben bewältigen kann von denen ich vorher nicht allzu viel wusste. Darunter zählen namentlich:
Obwohl durch die Neuerungen vieles einfacher ist und die bisherigen Praktiken nicht bekannt sein müssen bin ich ein Verfechter dafür dass die Grundlagen auf denen die “modernen Ansätze” aufbauen zumindest teilweise verstanden werden. Nur so ist es möglich diese auch richtig und vor Allem für den gedachten Verwendungszweck einzusetzen.
Das wollte ich nur mal posten – geht ja dank LiveWriter sehr einfach (das Warten hat sich gelohnt ;) Liebe Grüße July 02 Warum hat jede .net Anwendung mindestens 3 Threads?Ich wurde mal gefragt warum eine Konsolenanwendung 3 Threads hat auch wenn der Code ganz simpel ist. ZB 1: class Program 2: {3: static void Main() 4: { 5: Console.ReadKey(); 6: } 7: }Darin passiert nichts außer dass gewartet wird bis der Benutzer eine Taste drückt. Im Taskmanager ist deutlich zu erkennen dass 3 Threads laufen (Abbildung wird hier keine gezeigt, aber es leicht selbst zu prüfen ;) Aber warum 3? Die Frage lässt sich leich beantworten wenn man weiß wie die Common Language Runtime (CLR – die virtuelle Umgebung in .net) funktioniert. Auf detaillierte Ausführungen will ich verzichten und die Frage einfach beantworten:
Eigentlich logisch – oder? Happy coding ;) |
|
|