Für alle, die aus ihrem "Extra-Tool" keine Fortschrittsleiste rauskriegen:
- PowerShell kennt ein cmdlet Write-Progress. Damit kann man auf den Progress-Ausgabestrom schreiben und erhält eine Progressbar am oberen Fensterrand (des Konsolenfensters).
- Write-Progress ist aber ein simples cmdlet. Man muß es aufrufen, und damit es etwas Erkennbares tut, muß es in einer Schleife ausgeführt werden (egal wie die beschaffen ist). Hier gibt es jetzt zwei grundlegende Ansätze, mit denen man seine Progressbar ansteuern kann.
A
Wir haben eine Serie von (spätestens zur Laufzeit bestimmbar) vielen einzelnen Aktionen, deren Ausführungsstand wir mit Write-Progress dokumentieren wollen. Das ist der
synchrone Ansatz.
Beispiel: Alle Dateien aus einer Ordnerstruktur sollen gelöscht werden.
Implementierung (grundlegend; das Beispiel ist konstruiert):
PowerShell:
$Files = Get-Childitem -Recurse -Path C:\ # Bestimme Menge von zu verarbeitenden Objekten
1 .. $Files.Count | # Modell einer index-basierten FOR-Schleife in PS
% {
[int] $index = $_
Write-Progress -PercentComplete ( $index * 100 / Files.Count ) .... # Standardisiere Position in der Liste auf einen Prozentwert (als ganze Zahl)
$Files[$index] | Remove-Item # Eigentliche Aktion
}
Vorteil: Der Ansatz ist ziemlich einfach und funktioniert fast überall,
wenn die Vorbedingungen stimmen und ich
Zugriff auf die Schleife hab.
Nachteil: Ich brauche Zugriff auf die Schleife. Einen einzelnen lange dauernden Prozeß kann ich so nicht visualisieren.
B
Für einzelne, lange dauernde Prozesse brauche ich einen
asynchronen Ansatz. Beispiel: Ich will eine Progressbar haben für die Position in einer Mediendatei.
Hier muß ich mit Threads arbeiten oder eine der Möglichkeiten von PS nutzen, eine Aufgabe im Hintergrund ausführen zu können. Das geht unter anderem mit den *-Job cmdlets.
Dieser Ansatz ist individueller. Er funktioniert fast immer, solange ich nur in der Lage bin, Fortschrittsinformationen aus dem laufenden Task abzuleiten oder selbst zu generieren. Hierbei ist aber darauf zu achten, daß der Abgleich nicht aufwendiger wird als der eigentliche Task, denn sonst warte ich auf die Synchronisation und nicht auf die Erledigung meiner Aufgabe.
Der Nachteil ist, daß es kaum pauschale Vorgehensweisen gibt. Man muß für jedes Problem einen eigenen Abgleich finden, je nachdem, was mir die Implementierung meines Tasks ermöglicht und was nicht.
Beispiel 1
Ich hab die (fiktiven) Methoden zur Taskausführung
++ object Execute()
++ Task BeginExecute() und
++ object EndExecute(Task) zur Verfügung.
Execute() ist die synchrone Variante - die Aktion wird ausgeführt und ich warte aufs Ergebnis. Es gibt einen Rückgabewert (das Ergebnis). Das ist das normale Vorgehen.
BeginExecute() ist die asynchrone Variante. Die liefert ein Taskobjekt, welches mir sagt, was der Prozeß grad macht.
EndExecute(Task) schließt den Task ab.
Für den Fortschritt kann ich jetzt
- BeginExecute ausführen. Die Aufgabe wird gestartet und im Hintergrund ausgeführt.
- Write-Progress ausführen auf Basis der Information aus dem Task-Objekt. Write-Progress läuft nun parallel zur Aufgabe.
Problem: Wenn das Taskobjekt mir keine Infos liefert zum Umfang der Aufgabe, dann muß ich das vorher bestimmen und das kostet wieder Zeit; Zeit, in der die Aufgabe schon hätte ausgeführt werden können.
Beispiel 2
ich hab solche Statusinformationen
nicht zur Verfügung. Dann muß ich den Status zur Laufzeit selber ermitteln und mich fragen, wie ich das am besten anstelle, ohne daß mir das zeitlich aus dem Ruder läuft.
Also fange ich einfach mit der Aufgabe an und laß die im Hintergrund laufen.
Und jetzt hole ich mir den Fortschritt selber zur Laufzeit. Was wurde schon verarbeitet? Was nicht? Das ist mein Fortschritt und den muß ich mit Write-Progress visualisieren.
Dabei muß ich aber aufpassen, daß ich keinen Deadlock verursache, weil mein Überwachungstask mir Dinge blockiert, die der Ausführungstask selber blockieren muß.
Für diese Situation ist es daher im Gegensatz zum synchronen Ansatz vorteilhaft, sich seine Updatehäufigkeit für den Fortschrittsbalken gut zu überlegen. Der synchrone Ansatz aktualisiert mit erledigter Teilaufgabe. Die haben wir hier aber nicht. Stattdessen müssen wir uns das Teilergebnis periodisch selber ermitteln und wenn wir das einmal pro Millisekunde tun würden, haben wir wenig Informationsgewinn und viel Overhead; wenn wir aber zu selten prüfen, ist die Aufgabe fertig, ohne daß wir den Fortschrittsbalken zu Gesicht bekommen.
C
Der Multithreaded-Ansatz. Der ist gleichzeitig "einfach" und "der schwerste". Ich muß mein Problem in Teilaufgaben zerlegen und versorge Write-Progress mit der Anzahl der erledigten Teilaufgaben.
Beispiel: Ich will einen Ordner samt Unterordnern löschen.
Also erstelle ich einen Task für jeden unmittelbaren Unterordner und lösche die jeweils rekursiv. Als Ordnerstruktur komm ich nicht in die Verlegenheit, daß sich die einzelnen Tasks gegenseitig stören.
Meine Ausgabe für Write-Progress wird jetzt den Verlauf abschätzen, indem nicht die Anzahl der Dateien, sondern die jeweils erfolgreich gelöschten Unterordner protokolliert werden; das hängt dann davon ab, wieviele Einzeltasks ich erstellen konnte. Nur einer und ich hab gar nichts gewonnen.