30

I want to parallelize some file-parsing actions with network activity in powershell. Quick google for it, start-thread looked like a solution, but:

The term 'start-thread' is not recognized as the name of a cmdlet, function, script file, or operable program. Check the spelling of the name, or if a path was included, verify that the path is correct and try again.

The same thing happened when I tried start-job.

I also tried fiddling around with System.Threading.Thread

[System.Reflection.Assembly]::LoadWithPartialName("System.Threading")
#This next errors, something about the arguments I can't figure out from the documentation of .NET
$tstart = new-object System.Threading.ThreadStart({DoSomething}) 
$thread = new-object System.Threading.Thread($tstart) 
$thread.Start()

So, I think the best would be to know what I do wrong when I use start-thread, because it seems to work for other people. I use v2.0 and I don't need downward compatibility.

2
  • tl;dr: receive-job (wait-job ($a = start-job { "heyo!" })); remove-job $a or $a = start-job { "heyo!" }; wait-job $a; receive-job $a; remove-job $a Note also that if you call receive-job before the job is finished, you might get nothing at all. Commented Nov 22, 2017 at 18:46
  • Also (get-job $a).jobstateinfo.state; Commented Nov 22, 2017 at 19:02

5 Answers 5

33

Powershell does not have a built-in command named Start-Thread.

V2.0 does, however, have PowerShell jobs, which can run in the background, and can be considered the equivalent of a thread. You have the following commands at your disposal for working with jobs:

Name                              Category  Synopsis
----                              --------  --------
Start-Job                         Cmdlet    Starts a Windows PowerShell background job.
Get-Job                           Cmdlet    Gets Windows PowerShell background jobs that are running in the current ...
Receive-Job                       Cmdlet    Gets the results of the Windows PowerShell background jobs in the curren...
Stop-Job                          Cmdlet    Stops a Windows PowerShell background job.
Wait-Job                          Cmdlet    Suppresses the command prompt until one or all of the Windows PowerShell...
Remove-Job                        Cmdlet    Deletes a Windows PowerShell background job.

Here is an example on how to work with it. To start a job, use start-job and pass a script block which contains the code you want run asynchronously:

$job  = start-job { get-childitem . -recurse }

This command will start a job, that gets all children under the current directory recursively, and you will be returned to the command line immediately.

You can examine the $job variable to see if the job has finished, etc. If you want to wait for a job to finish, use:

wait-job $job

Finally, to receive the results from a job, use:

receive-job $job
Sign up to request clarification or add additional context in comments.

3 Comments

Start-Job actually starts a new Powershell session and it's heavy. It doesn't start a thread.
Start-Job does not capture any environment state, no locals, functions or module imports, has a huge overhead and is a typical sucking PowerShell API. It would be really helpful If .NET TPL could be used with some dispatching to PS runspace strategy
Please revise this answer, because there is Start-ThreadJob since powershell 6 and foreach -parallel since Powershell 7
9

You can't use threads directly like this, but you can't be blamed for trying since once the whole BCL is lying in front of you it's not entirely silly to expect most of it to work :)

PowerShell runs scriptblocks in pipelines which in turn require runspaces to execute them. I blogged about how to roll your own MT scripts some time ago for v2 ctp3, but the technique (and API) is still the same. The main tools are the [runspacefactory] and [powershell] types. Take a look here:

http://www.nivot.org/2009/01/22/CTP3TheRunspaceFactoryAndPowerShellAccelerators.aspx

The above is the most lightweight way to approach MT scripting. There is background job support in v2 by way of start-job, get-job but I figured you already spotted that and saw that they are fairly heavyweight.

Comments

4

The thing that comes closest to threads and is way more performant than jobs is PowerShell runspaces.

Here is a very basic example:

# the number of threads
$count = 10

# the pool will manage the parallel execution
$pool = [RunspaceFactory]::CreateRunspacePool(1, $count)
$pool.Open()

try {        
    # create and run the jobs to be run in parallel
    $jobs = New-Object object[] $count
    for ($i = 0; $i -lt $count; $i++) {
        $ps = [PowerShell]::Create()
        $ps.RunspacePool = $pool

        # add the script block to run
        [void]$ps.AddScript({
            param($Index)
            Write-Output "Index: $index"
        })

        # optional: add parameters
        [void]$ps.AddParameter("Index", $i)

        # start async execution
        $jobs[$i] = [PSCustomObject]@{
            PowerShell = $ps
            AsyncResult = $ps.BeginInvoke()
        }
    }
    foreach ($job in $jobs) {
        try {
            # wait for completion
            [void]$job.AsyncResult.AsyncWaitHandle.WaitOne()

            # get results
            $job.PowerShell.EndInvoke($job.AsyncResult)
        }
        catch {
            Write-Error $_
        }
        finally {
            $job.AsyncResult.AsyncWaitHandle.Dispose()
            $job.PowerShell.Dispose()
        }
    }
}
finally {
    $pool.Dispose()
}

It also allows you to do more advanced things like

  • Throttle the number of parallel runspaces on the pool
  • Import functions and variables from the current session

etc.

Comments

3

The answer, now, is quite simple with the ThreadJob module according to Microsoft Docs.

Install-Module -Name ThreadJob -Confirm:$true

$Job1 = Start-ThreadJob `
                -FilePath $YourThreadJob `
                -ArgumentList @("A", "B")
$Job1 | Get-Job
$Job1 | Receive-Job

4 Comments

:D 9 years, 7 months. So what does this do? Start a subshell of the same script with different parameters?
yes, you can start as many thread as you need, of course not just calling yourself (my original sample was badly recursive)
No, it doesn't do that. it runs in the same process
Note that this is for Powershell-Core
0

This is a working code to use System.Threading.Thread in Powershell. I think you can replace [ScriptBlock] by any [System.Object] that has a at least one method, and invoke the method you want in the new ThreadStart. You can also create a custom class with a method and use this class as object.

using assembly System;

$MainThread = [System.Threading.Thread]::CurrentThread | Out-String;
[Console]::Write($MainThread);

#Create a ScriptBlock for the second Thread.

[ScriptBlock]$Scr = {
    [System.Threading.Thread]::Sleep(4000);
    $SecondThread = [System.Threading.Thread]::CurrentThread | Out-String;
    [Console]::Write($SecondThread);
    [Console]::Write($s);
}

#Create the Thread and start it.

[IntPtr]$IntPtr = $Scr.GetType().GetMethod("Invoke").MethodHandle.GetFunctionPointer();

$ThreadStart = New-Object System.Threading.ThreadStart($Scr, $IntPtr);

$Thread = [System.Threading.Thread]::new($ThreadStart);

$Thread.Start();

#Return to the MainThread.

[System.Threading.Thread]::Sleep(500);
Write-Host 1;
[System.Threading.Thread]::Sleep(500);
Write-Host 2;
[System.Threading.Thread]::Sleep(500);
Write-Host 3;
[System.Threading.Thread]::Sleep(500);
Write-Host 4;
Read-Host;

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.