emeriks
Goto Top

PowerShell: In einen Job eine Variable auf Script-Ebene ändern

Hi,
gegeben sei in PowerShell auf Script-Ebene ein Array mit tausenden Objekten.
$A = @()
$A += New-Object PSObject -Property @{Prop1=$null; Prop2=$null}
$A += New-Object PSObject -Property @{Prop1=$null; Prop2=$null}
$A += New-Object PSObject -Property @{Prop1=$null; Prop2=$null}
$A += New-Object PSObject -Property @{Prop1=$null; Prop2=$null}
....
Jetzt sollen zwei Jobs diese Objekte parallel bearbeiten. Der eine Job die Prop1, der andere die Prop2.
z.B.
$Job1 = Start-Job -ScriptBlock {$A | %{$_.Prop1 = "Eins"}}  
$Job2 = Start-Job -ScriptBlock {$A | %{$_.Prop2 = "Zwei"}}  
Nach dem Ende der beiden Jobs soll auf Script-Ebene in den Elementen von $A die Properties Prop1 = "Eins" und Prop2 = "Zwei" sein.

Ich habe es mit den Scopes "script:" und "global:" versucht, aber beides funktioniert nicht.

Wie muss ich das anstellen, damit das so funktioniert, wie ich mir das vorstelle?

E.

Content-ID: 7947992246

Url: https://administrator.de/contentid/7947992246

Ausgedruckt am: 18.11.2024 um 13:11 Uhr

7907292512
7907292512 25.07.2023 aktualisiert um 15:39:49 Uhr
Goto Top
Moin.
Jobs laufen in separaten Threads darin kannst du keine Variablen der höheren Ebene verändern das ergäbe einen Illegal Cross-Thread-Call. Was du dort machen kannst ist ein Ergebnis zurückzugeben und mit Receive-Job das Ergebnis abzufragen und im Parent-Thread wieder einer Variablen zuweisen.
Aber für tausende Objekte sind "Powershell-Jobs" eh nicht so gut geeignet, da nimmt man besser gleich Runspaces/Pools die sind wesentlich schneller vor allem wenn man diese dann auch noch mit einem Runspace-Pool kombiniert. Dort kannst du auch mit Synchronized Hashtables arbeiten.

Hier mal ein Beispiel wie man aus einem separaten Thread Eigenschaften des Parent-Threads mittels Synchronized Hashtable ändern kann:
# create form
$form = [System.Windows.Forms.Form]@{
    Text = "Windows Forms Multi-Threading"  
    Size = '450,150'  
}
# create label
$label = [System.Windows.Forms.Label]@{
    Name = 'label1'  
    Location = '10,10'  
    Text = "Click me to start background task"  
    Dock = 'Fill'  
    TextAlign = 'MiddleCenter'  
    Font = [System.Drawing.Font]::new('Calibri',15)  
}

# add click event to the label
$label.add_Click({
    if ($cmd.InvocationStateInfo.State -ne 'Running'){  
        # start background script
        $result = $cmd.BeginInvoke()
    }
})

# event when closing form
$form.add_Closed({
    # end backgound script if it's still running 
    if ($cmd.InvocationStateInfo.State -ne 'Completed'){  
        $cmd.Stop()
    }
    $cmd.Dispose()
})

# create synchronized hashtable for bidirectional communication across threads
$ht = [hashtable]::Synchronized(@{})
# create runspace
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"  
$newRunspace.ThreadOptions = "ReuseThread"  
$newRunspace.Open()
# populate the synchronized Hashtable Variable to the runspace (make it available to the script)
$newRunspace.SessionStateProxy.SetVariable("ht",$ht)  
# add the whole form object to the hashtable, so we can access the whole form including it's nested controls 
$ht.Form = $form

# create new powershell process
$cmd = [powershell]::Create()
# define the script to execute in the process
[void]$cmd.AddScript({
    # everything here is executed in another thread
    # example countdown
    3..1 | %{
        # use the created object in the hashtable to change text in the label of the form
        $ht.Form.Controls['label1'].Text = "Background-Task is running, wait $_ seconds ..."  
        sleep 1
    }
    $ht.Form.Controls['label1'].Text = "Finished"  
    sleep 1
    $ht.Form.Controls['label1'].Text = "Click me to start background task"  
})
# assign the runspace to the process
$cmd.Runspace = $newRunspace

# add controls to the form
$form.Controls.AddRange(@($label))
# show form
[void]$form.ShowDialog()
Gruß siddius
TK1987
Lösung TK1987 25.07.2023 aktualisiert um 15:52:01 Uhr
Goto Top
Moin,

nimm statt dem normalen Array eine Synchronisierte ArrayList und weise diese einem Runspace als Variable zu.

$A = [System.Collections.ArrayList]::Synchronized(@())
$A += New-Object PSObject -Property @{Prop1=$null; Prop2=$null}
$A += New-Object PSObject -Property @{Prop1=$null; Prop2=$null}
$A += New-Object PSObject -Property @{Prop1=$null; Prop2=$null}
$A += New-Object PSObject -Property @{Prop1=$null; Prop2=$null}

$Job1 = [powershell]::Create().AddScript({$A | %{$_.Prop1 = "Eins"}})  
$Job1.Runspace.SessionStateProxy.SetVariable("A",$A)  
$Job2 = [powershell]::Create().AddScript({$A | %{$_.Prop2 = "Zwei"}})  
$Job2.Runspace.SessionStateProxy.SetVariable("A",$A)  

$Handle1 = $Job1.BeginInvoke()
$Handle2 = $Job2.BeginInvoke()

while (!$Handle1.IsCompleted -or !$Handle2.IsCompleted) {sleep 1}

$A
tio.run

Gruß Thomas
emeriks
emeriks 25.07.2023 um 16:00:40 Uhr
Goto Top
Danke!