<#
.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