Tahir Hassan
Tahir Hassan

Reputation: 5817

PowerShell: Cannot spawn a new thread

I am attempting to spawn a new thread in PowerShell's command line using:

$t = New-Object System.Threading.Thread ([System.Threading.ThreadStart]{ 
    Write-Host "Hello World" 
});

$t.Start();

What happens is that a dialog appears saying "Powershell has stopped working".

I want to use my own Job class, written in C#, with start, pause, continue and stop methods. It uses a couple of WaitHandles to achieve this together with a new Thead instance.

I am aware of Start-Job etc, but would like to use real threads.

Any way?

EDIT: There seems to be a way https://davewyatt.wordpress.com/2014/04/06/thread-synchronization-in-powershell/

Upvotes: 3

Views: 1184

Answers (1)

Tahir Hassan
Tahir Hassan

Reputation: 5817

UPDATE I have packaged the below into a module called PSRunspacedDelegate, which you can install using Install-Package PSRunspacedDelegate. You can find documentation on GitHub.


Adam Driscoll's PowerShell Parallel Foreach explains that a thread running PowerShell code must have a Runspace.

In other words [Runspace]::DefaultRunspace cannot be null.

I ended up writing a RunspacedDelegateModule.psm1 module, with a function New-RunspacedDelegate that does the work.

Add-Type -Path "$PSScriptRoot\RunspacedDelegateFactory.cs"

Function New-RunspacedDelegate(
    [Parameter(Mandatory=$true)][System.Delegate]$Delegate, 
    [Runspace]$Runspace=[Runspace]::DefaultRunspace) {

    [PowerShell.RunspacedDelegateFactory]::NewRunspacedDelegate($Delegate, $Runspace)
}

RunspacedDelegateFactory.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Management.Automation.Runspaces;

namespace PowerShell
{
    public class RunspacedDelegateFactory
    {
        public static Delegate NewRunspacedDelegate(Delegate _delegate, Runspace runspace)
        {
            Action setRunspace = () => Runspace.DefaultRunspace = runspace;

            return ConcatActionToDelegate(setRunspace, _delegate);
        }

        private static Expression ExpressionInvoke(Delegate _delegate, params Expression[] arguments)
        {
            var invokeMethod = _delegate.GetType().GetMethod("Invoke");

            return Expression.Call(Expression.Constant(_delegate), invokeMethod, arguments);
        }

        public static Delegate ConcatActionToDelegate(Action a, Delegate d)
        {
            var parameters =
                d.GetType().GetMethod("Invoke").GetParameters()
                .Select(p => Expression.Parameter(p.ParameterType, p.Name))
                .ToArray();

            Expression body = Expression.Block(ExpressionInvoke(a), ExpressionInvoke(d, parameters));

            var lambda = Expression.Lambda(d.GetType(), body, parameters);

            var compiled = lambda.Compile();

            return compiled;
        }
    }
}

What I noticed is that it would still crash if I used Write-Host, but Out-File seems to be ok.

Here is how to use it:

Import-Module RunspacedDelegateModule;
$writeHello = New-RunspacedDelegate ([System.Threading.ThreadStart]{ 
    "$([DateTime]::Now) hello world" | Out-File "C:\Temp\log.txt" -Append -Encoding utf8 
});
$t = New-Object System.Threading.Thread $writeHello;
$t.Start();

Upvotes: 4

Related Questions