Immediately Output Runspace Results to the Pipeline
In my previous post, I presented the template I use anytime I need to add multithreading to my scripts.
Recently, I had a request to add multi-threading to Read-DbaBackupHeader. This was the first runspace in which I had to output the results from the runspace to the host -- usually I just needed commands to run in the background, like with bulk-inserts to SQL Server.
So two interesting things came out of this particular change. First, I made use of the time that was spent waiting for runspaces to finish.
1# BLOCK 5: Wait for runspaces to finish
2 while ($runspaces.Status.IsCompleted -notcontains $true) {}
Now, I'm basically doing this
1# BLOCK 5: Wait for runspaces to finish
2 while ($runspaces.Status.IsCompleted -notcontains $true) { $runspaceresult }
Ultimately, though, this is what I had to do to get the output right to screen.
1# BLOCK 5: Wait for runspaces to finish
2while ($runspaces.Status -ne $null)
3{
4 $completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
5 foreach ($runspace in $completed)
6 {
7 $runspace.Pipe.EndInvoke($runspace.Status)
8 $runspace.Status = $null
9 }
10}
Now, I came upon an issue with the $restore sometimes not returning anything. I'm not sure why; perhaps it has something to do with pooling. SQL Server didn't return an error reading the backup header, it just returned nothing. To handle this issue, I basically restarted the runspace if the result came back as null. The code is here if you'd like to see how I handled it.
Copy/Pastable Code
I generally like to provide code that actually does something useful but in this case, it just complicated things. So this outputs text -- but it does it directly to the pipeline without waiting until all runspaces have finished.
1$pool = [RunspaceFactory]::CreateRunspacePool(1, [int]$env:NUMBER_OF_PROCESSORS + 1)
2$pool.ApartmentState = "MTA"
3$pool.Open()
4$runspaces = @()
5
6$scriptblock = {
7 Param (
8 [string]$server,
9 [int]$count
10 )
11 # Pretend I connected to a server here and gathered some info
12 Write-Output "Pretended to connect to $server $count times"
13}
14
151..10 | ForEach-Object {
16 $runspace = [PowerShell]::Create()
17 $null = $runspace.AddScript($scriptblock)
18 $null = $runspace.AddArgument("sql2016")
19 $null = $runspace.AddArgument(++$i)
20 $runspace.RunspacePool = $pool
21 $runspaces += [PSCustomObject]@{ Pipe = $runspace; Status = $runspace.BeginInvoke() }
22}
23
24while ($runspaces.Status -ne $null)
25{
26 $completed = $runspaces | Where-Object { $_.Status.IsCompleted -eq $true }
27 foreach ($runspace in $completed)
28 {
29 $runspace.Pipe.EndInvoke($runspace.Status)
30 $runspace.Status = $null
31 }
32}
33
34$pool.Close()
35$pool.Dispose()
If you'd like to see this with a couple more comments, check out the gist.
Hmm, I wonder if this runspace will be easier for others to understand. Looks like I'll be linking this blog post in my previous one. Hope this helped!