"Start-Job" Aufgaben auslagern

Homeboy12

Cadet 2nd Year
Registriert
Dez. 2019
Beiträge
22
Ich habe ein relativ aufwändiges PS GUI gebastelt.
Ein Tool um grundsätzlich erstmal bestimmt Ordner zu kopieren.
Das GUI Oberfläche scheint sich jedoch auf zu hängen, sobald der Kopier Button gedrückt wird.
Die Progressbar ändert sich nicht mehr bzw. wandert nicht weiter.
Nach langem hin und her kann ich die Kopiervorgänge an neue Jobs übermitteln, eben mit Start-Job.
Nun habe ich es nicht geschafft die Ziel- und Quellpfade an den neuen Job zu übergeben, weshalb ich sie erst in eine Txt schreibe und im neuen Job einlese.
So lange dieser andere Job läuft, gehe ich davon aus, dass er noch am Kopieren ist, also lasse ich den Job Status im Hauptscript per while-Schleife solange abfragen bis sich der Status ändert.
Ändert sich der Status, dann prüfe ich die Ordner Größen der Quelle und der Kopie. Sind beide gleich, dann ist der Vorgang erfolgreich abgeschlossen.
Ist es möglich dem neuen Job Variablen mit zugeben oder zu entnehmen?
Oder gibt es andere Tricks zu verhindern, dass sich mein GUI aufhängt?

Besten Dank für eure Hilfe!
 
Jetzt bin ich aber doch mal neugierig, vor allem, da ein Codeausschnitt fehlt.
Wie habt ihr denn Events verknüpft? Und was machen die?
Nach meinem Stand läuft bei ps erstmal alles in einem Thread. Entsprechend muß die gui auf Eventhandler warten.

Eine Option sind PS runspaces. Das sind weitere Interpreterinstanzen im selben Prozeß, ähnlich wie bei Perl.
Die sind bissel umständlich. Evtl gibts dazu was in der PSGallery. Ansonsten selber bauen.

Das ist das beste, was ps hat in Richtung multithreading. Wenn man das hat, baut man einen Eventhandler, der für das Handling einen neuen thread startet. Dann wartet die gui nicht, man muß aber den Thread überwachen und aufpassen, daß man zb das Control deaktiviert, bis der Handler abgeschlossen ist. Sonst würde der Anwender den Knopf fünfmal drücken können, während die Aktion noch läuft und der Handler kommt doch selbst in die Quere.
 
Ok.
Folgende Zwischenlösung konnte ich erstellen:
Code:
$quelle = $env:USERPROFILE + "\Bilder"
$ziel = "D:\"
$scriptblock1 = [scriptblock]::Create((Copy-Item -Path $quelle -Destination $ziel -Recurse))
$job1 = Start-Job -ScriptBlock $scriptblock1
Do {[System.Windows.Forms.Application]::DoEvents()} Until ($job1.State -eq "Completed")
Doch erhalte ich diesen Fehler:
Code:
Ausnahme beim Aufrufen von "Create" mit 1 Argument(en):  "Der Objektverweis wurde nicht auf eine
Objektinstanz festgelegt."
In Zeile:4 Zeichen:1
+ $sb = [scriptblock]::Create((Copy-Item -Path $v3 -Destination $v4 -Re ...
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    + CategoryInfo          : NotSpecified: (:) [], MethodInvocationException
    + FullyQualifiedErrorId : NullReferenceException

Quelle:
https://stackoverflow.com/questions...uate-variables-in-a-scriptblock-for-start-job
 
So richtig seh ich da nicht durch.

Zunächst: wenn Du dem Scriptblock nicht sagst, was da was ist, dann sind die Variablen natürlich leer. Der Scriptblock wird nicht bei Erstellung, sondern bei Ausführung ausgewertet. Parameterübergabe ist deshalb NICHT optional.

Beispielsweise so:
Code:
$quelle = $env:USERPROFILE + "\Bilder"
$ziel = "D:\"
# Scriptblock::Create will einen String
$scriptblock1 = [scriptblock]::Create("Copy-Item -Path $quelle -Destination $ziel -Recurse")
# Es geht aber auch einfacher:

$scriptblock2 = {
Copy-Item -Path $quelle -Destination $ziel -Recurse
}

# Das mit dem Scriptblock::Create und der Hoffnung, man werde schon das richtige kriegen, geht in den allermeisten Fällen schief. Stattdessen: explizite Zuweisung.
# Dazu müssen wir den scriptblock von oben aber erweitern. Er sieht dann wie eine Funktion aus.

$scriptblock3 = {
Param
(
#  Deklariere Übergabeparameter - alles das, was innerhalb des Scriptblocks nicht bekannt ist und nie zugewiesen wird, sprich was bei einer Funktion die Parameterliste wäre
[string] $Quelle
[string] $Ziel
)

Copy-Item -Path $quelle -Destination $ziel -Recurse

}

# Und jetzt übergeben wir das. Problem: PS kennt (und bevorzugt) zwar namensbasierte Parametrisierung, aber exakt hier funktioniert das NICHT; stattdessen muß man POSITIONAL rangehen.
# Wir haben im Scriptblock ZUERST $Quelle und DANN $Ziel deklariert. Also muß zuerst Quelle und dann Ziel gesagt werden.

<# GANZ WICHTIG: Die Variablen $Quelle und $Ziel im Scriptblock haben zwar denselben Namen wie die Variablen Quelle $und $Ziel außerhalb.
Es sind aber gänzlich ANDERE Variablen. **Aufpassen!**
#>

$job1 = Start-Job -ScriptBlock $scriptblock3 -Argumentlist $Quelle, $Ziel


Als Nächstes hab ich hier eine ganz simple Implementierung von PS Runspaces. Die macht nichts weiter, als einen neuen Pool aufzumachen und einen neuen Task darin auszuführen. Runspaces können signifiikant mehr, aber es soll auch eher der Veranschaulichung dienen (und kann natürlich als Grundlage für den eigenen Einsatz dienen).
Hinweis: die Funktion war Teil meiner eigenen Scriptbibliothek und Teil eines Moduls; letzter Stand Mai 2018 und seit einer ganzen Weile nicht mehr im Gebrauch.
Ergo Status: ungetestet.
Wie immer gilt: nicht einfach irgendwelchen Code ausführen, den irgendwer im Internet postet.

Das Ganze kann mit . <datei.ps1> in die Umgebung geladen oder sonst als Funktion irgendwo eingefügt werden.

PowerShell:
<#
.Example
$result = Start-Thread -ScriptBlock { Get-ChildItem };
while($result.Thread.IsCompleted -eq $false)
{
Start-Sleep -Seconds 1;
}
[System.Io.FileSystemInfo[]] $output = $result.PS.EndInvoke($result.Thread);
$result.PS.Dispose();

Sample application of Start-Thread. The result (of the passed script block) ends up in $output and is typed as if script block had been run synchronously (in this case, a list of file and folder objects; that is, [FileSystemInfo[]]).
.Description
IMPORTANT.
Start-Thread is a proof-of-concept implementation of PowerShell threading. Look into PsThreads for a more complete implementation.
Start-Thread SHOULD NOT be used in production-grade scripts, because while it is able to START (new) threads, it cannot STOP them,
nor can it properly handle the underlying thread pool, up to and including Close()ing it.
             Something that, eventually, after sufficiently many calls to Start-Thread, WILL lead to memory overflow.
Start-Thread can, however, be used to test things easily.

PowerShell threading works by starting another PowerShell instance within the host process
and then passing commands to that instance. In some ways, this is similar to virtual consoles,
although PowerShell permits passing commands between these instances.
Note that this DIFFERENT from system threads. In particular:
--- If an external (non-PowerShell) command, application, or similar, is to be run through PowerShell threads,
--- then that command must be handled in a way that the PowerShell instance it was executed from
--- does NOT terminate BEFORE the command itself UNLESS return values or output do not matter.
--- If access to return values or output of that external tool or program is required, then it must be started
--- SYNCHRONOUSLY to that PS thread.
Process flow of PS threading:
    *** Initial state
        ====> (PS host process)
    *** A new thread is invoked:
=========> (ps host process)
            |====> (invoked PS thread)
    *** That thread launches an external tool
        =============> (ps host process)
|========> (invoked PS thread)
                |====> (PS thread invoked external tool)
    **** > External tool runs ASYNChronously to that thread
========================================================> (ps host process)
|========================X (invoked PS thread terminates upon conclusion of passed script block)
                |===============================================> (Ps thread invoked external tool)
        XXXX In this scenario, when the PS thread terminates, it cannot pass information on its child back to its parent because the child thread is still running after the parent has died

    **** > External tool runs SYNChronously to that thread
========================================================> (ps host process)
|=========================================X (invoked PS thread terminates upon conclusion of passed script block)
                |====================================X (Ps thread invoked external tool)
        XXXX In this scenario, the PS thread is told to -Wait (via Start-Process, for example) for the termination of its child.
Thus the PS thread receives information on the child's termination (including, but not limited to, possible output data)
             which it can then hand back to its parent (the host process) along with information on its own termination.

If no information on the external tool's execution state is required, then it can run asynchronously to its parent thread.
There is nothing to gain from doing this, however. In cases where it isn't necessary to wait for the conclusion of a particular command, that command can be put into its own PS thread and run there synchronously, returning information on success or failure (if nothing else) on conclusion.
Running the tool asynchronously to the PS thread just means we don't care about what that tool does,
what (if anything) it returns, or whether it even did what it was supposed to;
because any exceptions thrown, any output returned, any status information generated we cannot catch
because the thread that was supposed to do that for us has died
shortly after invoking the tool at the earliest; or
the termination of the passed script block at the latest, whichever comes later.
And since the tool was run asynchronously, neither depends in any way
on the termination (or non-termination) of the tool.
Also note that upon conclusion, the $Pool should be disposed of to avoid memory hogging of the PowerShell process itself.
Start-Thread does not -- cannot -- implement this, since to Close() the pool would also lose us the thread.
Look at PsThreads module for that.
#>
function Start-Thread
{
[cmdletbinding()]
[outputtype([pscustomobject])] # A [powershellasyncresult], but it's not clear where that one actually *lives*.
Param
(
[Parameter(Mandatory=$true,ParameterSetName='byScriptBlock')] # Passing parameter set names disables auto-positioning of parameters
[ValidateNotNullOrEmpty()]
[scriptblock]$ScriptBlock,
[Parameter(Mandatory=$false)]
[Collections.IDictionary]$ArgumentList = @{},
[Parameter(Mandatory=$false)]
[ValidateScript({$_ -ge 0})]
[int]$minPools = 1,
[Parameter(Mandatory=$false)]
[ValidateScript({$_ -gt 0})]
[int]$maxPools = 5
)
# min/maxpools may be of type int but < 0 doesn't make sense.
# Therefore, minpools has been set to be at least 0 and maxpools at least 1.
[Management.Automation.Runspaces.RunspacePool]$pool = [runspacefactory]::CreateRunspacePool($MinPools,$MaxPools)
#[initialsessionstate]$sessionState = [initialsessionstate]::CreateDefault();
if($pool.GetAvailableRunspaces() -gt 0)
{

[powershell]$PS = [powershell]::Create()

$pool.Open()

$ps.RunspacePool = $pool

$null = $ps.AddScript($scriptblock).AddParameters($ArgumentList)

    [object] $thread = $ps.BeginInvoke()
Write-Verbose -Message 'Thread started'
# Output
New-Object -TypeName PsCustomObject |
Add-member -MemberType NoteProperty -Name PS -Value $PS -PassThru |
    Add-Member -MemberType NoteProperty -Name Thread -Value $thread -PassThru

} # have runspaces available
} # Start-Thread

Die Funktion liefert ein Custom-Objekt mit zwei Eigenschaften
.PS => ein Verweis auf die Powershell-Instanz, in der der Thread läuft. Die ist von der aktuellen Instanz verschieden, die eigene Instanz hat keinen Zugriff auf den Thread.

.Thread => ein Verweis auf den Thread.

Verwendung (wie gesagt, eine sehr simple Implementierung)
PowerShell:
get-command start-thread -syntax
Start-Thread -ScriptBlock <scriptblock> [-ArgumentList <IDictionary>] [-minPools <int>] [-maxPools <int>] [<CommonParameters>]

<#
-Scriptblock : wie oben
- Argumentlist: ist im Gegensatz zu oben ein DICTIONARY.
Schlüssel sind die Variablennamen im übergebenen Scriptblock. Werte sind die zugeordneten Werte.

- minPools / maxPools werden an der Stelle noch nicht genutzt: sie konfigurieren den Runspacepool per minimum und maximum an bereitzustellenden Threads. Es wird aber nur einer gestartet. Wer will, kann das Ganze erweitern auf echtes Multithreading mit mindestens minPools gleichzeitig auszuführenden  Threads und maxPools maximal gleichzeitig auszuführenden Threads.

Das Ergebnis **muß** zugewiesen werden.
#>

#Start:
$task = Start-Thread -Scriptblock { ls }

#Beenden:
# Hinweis: EndInvoke ist sicher für die Verwendung; es gibt erst dann zurück, wenn der Task abgeschlossen ist. Explizites Warten ist nicht erforderlich. Idealerweise ruft man das erst auf, wenn man das Ergebnis auch braucht, bis dahin darf der Thread asynchron laufen.

$result = $task.PS.EndInvoke($task.Thread)

# $Result hat jetzt dasjenige Ergebnis, was man ansonsten durch den "normalen" Aufruf bekommen hätte.
# PS nennt es allerdings PSDataCollection<type> statt <type>[] oder dergleichen. Kann man aber bedenkenlos ganz normal verwenden.

Wenn man die Funktion geladen hat, kriegt man mit Get-Help Start-Thread -Full meine Doku von damals. Die lesen und Caveats beachten.
 
Zuletzt bearbeitet von einem Moderator:
  • Gefällt mir
Reaktionen: Homeboy12
Zurück
Oben