PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : [.NET] Multi-Threading: Ping Anfrage


Monger
2009-01-28, 14:36:31
Servus!
Da ich mit Multithreading leider herzlich wenig Übung habe, wollte ich mal für folgendes Problem in die Runde fragen...

Ich hab hier eine kleine .NET Anwendung, mit einer Tabelle von Rechnernamen. Wenn ich auf einen Button drücke, möchte ich dass jeder dieser Rechner angepingt wird, und sich die Farbe im Grid entweder zu grün oder zu rot hinwechselt.

Soweit habe ich das auch bereits. Was ich nur gerne hätte: jeder Rechner soll gleichzeitig, und nicht etwa hintereinander angepingt werden. Schick wäre es auch noch, wenn ich eine Rückmeldung bekommen würde wenn ich fertig bin (Fortschrittsbalken) Und jetzt frage ich mich, wie ich das am geschicktesten mache.

Ich hab mir mal den Threadpool und den Backgroundworker angeschaut, bin aber noch nicht so überzeugt davon. Mit dem Backgroundworker kann ich mir ungefähr vorstellen wie das aussehen soll... aber wie setze ich intelligenterweise damit eine Fortschrittsanzeige bei mehreren parallelen Backgroundworkern um?

Was wäre denn der eleganteste und sauberste Weg (mit dem .NET Standardframework) um das umzusetzen?

DraconiX
2009-01-28, 15:16:49
Welche Sprache schreibst du? C#, VC, VB...

Kann man denn eigentlich die Thread's gezielt gleichzeitig laufen lassen? Ich meine sie werden ja auch nacheinander aufgerufen. Mit dem BW hab ich auch noch nichts gemacht.

Expandable
2009-01-28, 15:16:54
Am einfachsten wäre wohl, für jeden Rechner einen eigenen Backgroundworker zu starten, der den Ping ausführt. Danach wird einfach das Completed-Event des BGW aufgerufen. Deine GUI kann dann einfach den Fortschritt berechnen (sie kennt ja die Anzahl der bereits fertigen Pings und die Anzahl der Rechner insgesamt).

Das Pingen an sich ist vermutlich eine atomare Operation, von der du so ohne weiteres nicht irgendeinen Fortschritt erhalten wirst. Aber da weiß ich ja nicht, was du da machst.

Nachtrag: Die konkrete Sprache ist dafür natürlich völlig egal. Es kann also C#, VB.NET, C++/CLI, F# oder sonst was sein. Man kann Threads nie wirklich gleichzeitig laufen lassen - wie denn auch, wenn es 20 Threads gibt aber nur 2 CPUs. Wann ein Thread läuft ist Sache des Betriebssystem bzw. der CLR. Es könnte im worst case sogar sein, dass die Thread sequentiell ausgeführt werden.

Trap
2009-01-28, 15:29:00
Es könnte im worst case sogar sein, dass die Thread sequentiell ausgeführt werden.
Für ein OS, das sowas macht, gibt es sicher keine .NET-VM.

robobimbo
2009-01-28, 18:36:27
ich würd dir die parallel extensions empfehlen, da gibts auch ein paar gute screencasts dafür - sind im prinzip warper klassen die mit einem threadpool arbeiten und du generierst einfach tasks die abgearbeitet werden, sakliert sehr schön und liefert dir nebenbei konstrukte wie paralell.for usw alles mit

zB

http://channel9.msdn.com/posts/DanielMoth/Intro-to-Parallel-Extensions-to-the-NET-Framework/
http://channel9.msdn.com/posts/DanielMoth/Parallel-LINQ-PLINQ/
http://channel9.msdn.com/posts/DanielMoth/How-to-use-the-static-Parallel-class/
http://channel9.msdn.com/posts/DanielMoth/ParallelFX-Task-and-friends/

Monger
2009-01-28, 19:13:38
Bin leider auf .NET 2.0 festgelegt :( , und auch Extensions machen sich bei mir nicht so gut.

Aber trotzdem danke. Hab jetzt übrigens den Ansatz mit den Backgroundworkern ausprobiert, und sieht recht vielversprechend (und vorallem erfreulich simpel) aus.

Gast
2009-01-28, 19:52:41
Schau dir mal die ThreadPool Klasse an.

Monger
2009-01-29, 17:28:03
Ich tu mir irgendwie immer noch schwer. Das Problem ist, dass ich aus der DataGridview eigentlich was auslesen möchte, dann den Thread laufen lassen möchte, und die Ergebnisse dann ins DataGridview zurückschreiben möchte.

Dabei passieren mir aber diverse asynchrone Fehler. Wenn ich versuche, das zugrundeliegende DataSet im Thread zu manipulieren, landen die Ergebnisse zwar dort ordnungsgemäß, aber das neu zeichnen der Zellen funktioniert unzuverlässig. Wenn ich versuche, die nötigen Parameter als als struct Argument zu übergeben (ich muss ja z.B. mitschleifen, welche Zeile genau ich eigentlich später bearbeiten will), komme ich in irgendwelche Zugriffsfehler hinein.

Bin grad n bissl ratlos. Hat jemand vielleicht ein Patentrezept, wie man diesen Weg (grafische Oberfläche auslesen - asynchron berechnen - grafische Oberfläche setzen) sauber implementiert?

Abraxus
2009-01-29, 18:10:48
Ich tu mir irgendwie immer noch schwer. Das Problem ist, dass ich aus der DataGridview eigentlich was auslesen möchte, dann den Thread laufen lassen möchte, und die Ergebnisse dann ins DataGridview zurückschreiben möchte.

Dabei passieren mir aber diverse asynchrone Fehler. Wenn ich versuche, das zugrundeliegende DataSet im Thread zu manipulieren, landen die Ergebnisse zwar dort ordnungsgemäß, aber das neu zeichnen der Zellen funktioniert unzuverlässig. Wenn ich versuche, die nötigen Parameter als als struct Argument zu übergeben (ich muss ja z.B. mitschleifen, welche Zeile genau ich eigentlich später bearbeiten will), komme ich in irgendwelche Zugriffsfehler hinein.

Bin grad n bissl ratlos. Hat jemand vielleicht ein Patentrezept, wie man diesen Weg (grafische Oberfläche auslesen - asynchron berechnen - grafische Oberfläche setzen) sauber implementiert?
Probier es mal mit Software Transactional Memory (STM)
Es gibt verschiedene STM Implementierungen für .net. Unter anderem auch eine von MS-Research.
Wobei dir das eigentlich die Datenbank abnehmen sollte, die haben sowas wie STM schon sehr lange. Dann änder einfach die Werte direkt in der Datenbank.

Solange die Operationen nur sehr kurz sind reicht auch intelligentes, "lock (C#)" was dir hoffendlich ein Begriff sein sollte. Wobei locking ehrlich gesagt auch ziehmlich eklich sein kann.

Wenn mich nicht alles täuscht, verschiebt ein normales ".BeginInvoke()" die Aufgabe auch in den ThreadPool. Diese ganze BackgroundWorker Geschichte ist doch mit Kanonen auf Spatzen geschossen.

PatkIllA
2009-01-29, 18:24:50
GUI manupulieren aus anderen Threads geht nicht (zuverlässig). Dafür haben alle Controls eine Invoke-Methode.

Trap
2009-01-29, 19:24:19
Man muss den BackgroundWorker nur richtig benutzen. Veränderungen am Zustand des restlichen Programms muss man im Handler von RunWorkerCompleted machen, der läuft dann nämlich im GUI-Thread.

Monger
2009-01-29, 19:39:04
Das Problem scheint mir das rein- und rausreichen von Parametern zu sein. Irgendwo da breche ich offensichtlich die Threadgrenzen. Ich werd morgen mal meinen (gekürzten) Code hier posten, vielleicht kann mir ja jemand sagen was mein Denkfehler ist.

Gast
2009-01-29, 19:55:12
Monger,

verwende am besten die ThreadPool Klasse. Danach erstellst du auf Klassenebene ein normales Objekt vom Typ Object. In der Methode, die dann von der ThreadPool Klasse ausgeführt wird, erledigst du deine Aufgaben. Wenn du dann fertig bist und das ganze irgendwo synchronisieren möchtest, greifst du threadsicher auf das locker Objekt zu. Also ungefähr so (C#):

private object lockerObject = new object();


private void MeineThreadMethode()
{
// meine parallelen Aufgaben

lock(lockerObjekt)
{
// Hier kannst du dann threadsicher auf dein Dataset zugreifen.
// Wenn du auf die Gui zugreifen willst, dann musst du einen delegaten erstellen und mit Invoke aufrufen

}

}


Du brauchst dir übrigens nicht die Zeile vom Grid merken. Dazu könntest du auch einfach eine BindingSource verwenden, dann wird dein Grid automatisch upgedatet, wenn die Daten im dahinterliegenden DataSet sich geändert haben.

Monger
2009-01-29, 20:01:36
Du brauchst dir übrigens nicht die Zeile vom Grid merken. Dazu könntest du auch einfach eine BindingSource verwenden, dann wird dein Grid automatisch upgedatet, wenn die Daten im dahinterliegenden DataSet sich geändert haben.
Das hat wie gesagt nicht wirklich gut funktioniert. Ich weiß nicht genau wie die Signalisierung hintendran funktioniert, aber das daran gebundene DataGridView hat eben relativ unzuverlässig aktualisiert. Gut, vielleicht wäre es einfacher hintendran mit nem Repaint aufzuräumen, aber irgendwie kann das ja nicht die Lösung sein.

Noch dazu will ich an dieser Zeile noch DataGridview-spezifische Änderungen vornehmen, sprich: z.B. die Rechner die ich erreichen konnte, grün kennzeichnen. Dazu würde es sich natürlich anbieten, gleich die passende DataGridviewRow in der Hand zu haben.

Im übrigen: im DataSet stand ja immer alles drin. Da habe ich jetzt keine Synchronisationsprobleme feststellen können. Nur die Events die ein Neuzeichnen in der DataGridview auslösen sollten, haben sich wohl verschluckt.

Gast
2009-01-29, 20:42:52
Dann machst du eben etwas falsch und musst den Fehler suchen! Vielleicht musst du im DataSet noch AcceptChanges() aufrufen, damit die Änderungen gültig werden.

Ich würde trotzdem nicht von außerhalb das Grid manipulieren. Hänge dich da lieber an das CellFormatting Ereignis. Von dort greifst du über den aktuellen Row Index auf das dashinterliegende DataItem zu, z.B.:

private void GridViewJobs_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
if (e.Value == null)
return;

DataRow row = (DataRow)((DataRowView)this.gridView.Rows[e.RowIndex].DataBoundItem).Row;


if(rows["Statusflag"] == "ok")
// mache irgendwas

}

Monger
2009-01-30, 17:05:07
Okay, ich denke ich habs jetzt. Danke euch!

Im Endeffekt sieht mein Code jetzt ungefähr so aus (Pseudocode):


ButtonClick(){

for each row in DataSet{
b = new BackGroundWorker
b.DoWork += WorkerMethod
b.RunWorkerCompleted += postProcess
b.RunWorkerAsync(row)
}
}

private semaphore as new Object()

WorkerMethod(sender, args){
synchronized(semaphore){
args.Name = "Blah"
}
}

postProcess(sender, args){
ProgressBar.Value += 1
}

DataGridView1_CellFormatting(sender, args){
if (args.Value == "Online"){
DataGridview1.rows(args.rowindex).DefaultCellStyle.Backcolor = Green
}
}


Funktioniert sehr gut. Ist imho auch relativ schlank und übersichtlich. Der Backgroundworker ist doch ziemlich praktisch. Rückblickend gesehen bin ich über zwei Sachen gestolpert. Zum einen dachte ich nicht, dass ich auf das DataSet nochmal synchronisieren muss, und zum anderen habe ich partout kein passendes Event im Grid gefunden was anspringt wenn sich die Datenhaltung ändert.

Für beide Tipps nochmal herzlichen Dank!