使用运行空间池和 begininvoke 时如何收集返回值数据?

问题描述

我让这段代码与 powershell.Invoke() 同步运行良好,但是使用 powershell.BeginInvoke() 我无法捕获输出。要使用下面的代码,您需要填充 $servers 变量,否则它应该运行。

我尝试通过在使用 EndInvoke() 时将每个线程添加到 $threads 变量来捕获输出,并且我能够看到线程句柄和 iscompleted 值,但是我无法弄清楚我返回的值在哪里存储每个函数的返回部分。

一个块是我看到的输出,在一个异步完成之前显示为 false,直到它完成,然后所有线程句柄显示为 true。

谢谢!

8804 is True
16420 is True
13352 is True
11184 is True
3872 is True
8288 is True
17296 is False
20816 is True
11628 is True
17688 is True
12856 is True
19400 is True

8804 is True
16420 is True
13352 is True
11184 is True
3872 is True
8288 is True
17296 is True
20816 is True
11628 is True
17688 is True
12856 is True
19400 is True


Thread count: 12
Time elapsed: 3


cls;

$stopwatch =  [system.diagnostics.stopwatch]::StartNew();

#region Runspace Pool

[runspacefactory]::CreateRunspacePool() | Out-Null;
$SessionState = [System.Management.Automation.Runspaces.InitialSessionState]::CreateDefault();
$RunspacePool = [runspacefactory]::CreateRunspacePool(

    1,#Min Runspaces

    16 #Max Runspaces

);
$RunspacePool.open();

#endregion Runspace pool

$threads = New-Object System.Collections.ArrayList;
$servers = @("goodServer1","goodServer2","badServer1","goodServer3");

foreach ($server in $servers)
{
    $PowerShell = [powershell]::Create();
    $PowerShell.RunspacePool = $RunspacePool;

    [void]$PowerShell.AddScript({

        Param ($server,$portNumber)
        
        [pscustomobject]@{

            server = $server
            portNumber = $portNumber

        } | Out-Null
    
        Function testPort ($server,$portNumber)
        {
            $testPort = New-Object System.Net.sockets.TCPClient # -ArgumentList $server,3389;
            $testPort.SendTimeout = 3;
            try
            {
                $testPort.Connect($server,$portNumber);
            }
            catch
            {
                #do nothing;
            }
            $result = $testPort.Connected;
            $testPort.Close();          
            $dateTime = ([DateTime]::Now.ToString());
            
            return "$server|testPort|$result|$dateTime"; # server | function | result | DateTime
        }


        testPort -server $server -portNumber $portNumber;
    }) # end of add script

    $portNumber = "3389";
    $PowerShell.AddParameter('server',$server).AddParameter('portNumber',$portNumber) | Out-Null;
    $returnVal = $PowerShell.BeginInvoke();
    $temp = "" | Select PowerShell,returnVal;
    $temp.PowerShell = $PowerShell;
    $temp.returnVal = $returnVal;
    $threads.Add($Temp) | Out-Null;
    
    
    $PowerShell = [powershell]::Create();
    $PowerShell.RunspacePool = $RunspacePool;
    [void]$PowerShell.AddScript({

        Param ($server,$shareName,$timeOutInMs)
        
        [pscustomobject]@{

            server = $server
            shareName = $shareName
            timeOutInMs = $timeOutInMs

        } | Out-Null
    

        Function testShare ($server,$timeOutInMs)
        {
        $cSharp = 
@'
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;

namespace cSharp7
{
    public class cSharpClass
    {
        public bool verifyDirectoryExists(string uri,int timeoutInMs)
        {
            var task = new Task<bool>(() =>
            {
                var dir = new DirectoryInfo(uri);
                return dir.Exists;
            });
            task.Start();
            return task.Wait(timeoutInMs) && task.Result;
        }
        
        public bool verifyFileExists(string uri,int timeoutInMs)
        {
            var task = new Task<bool>(() =>
            {
                var fi = new FileInfo(uri);
                return fi.Exists;
            });
            task.Start();
            return task.Wait(timeoutInMs) && task.Result;
        }
    }
}
'@

            $assemblies = ("System","System.Collections","System.ComponentModel","System.Data","System.Drawing","System.Linq","System.Threading.Tasks","System.Windows.Forms","System.Management.Automation","System.Security","System.Threading","System.Collections.Concurrent","System.Security.Principal","System.Management","System.IO","System.Collections");
            Add-Type -TypeDeFinition $cSharp -ReferencedAssemblies $assemblies -Language CSharp;
            $directoryExists = New-Object CSharp7.cSharpClass;
            $path = "\\" + $server + "\" + $shareName;
            try
            {
                $result = $directoryExists.verifyDirectoryExists($path,$timeOutInMs); # has a 2 minute timeout period,needs an asynchronous thread with a timeout period
                #Write-Host $result;
            }
            catch
            {
                # do nothing
            }
            
            $dateTime = ([DateTime]::Now.ToString());
            return "$server|testShare|$result|$dateTime"; # server | function | result | DateTime
        }

        testShare -server $server -shareName $shareName -timeOutInMs $timeOutInMs;
    }) # end of add script

    $shareName = "c$";
    $timeOutInMs = "3000";
    $PowerShell.AddParameter('server',$server).AddParameter('shareName',$shareName).AddParameter('timeOutInMs',$timeOutInMs) | Out-Null;
    $returnVal = $PowerShell.BeginInvoke();
    $temp = "" | Select PowerShell,$pingCount)
        
        [pscustomobject]@{

            server = $server
            pingCount = $pingCount

        } | Out-Null
    

        Function testPing ($server,$pingCount)
        { 
            try
            {
                $result = Test-Connection $server -Count $pingCount -Quiet;
            }
            catch
            {
                # do nothing
            }
            
            $dateTime = ([DateTime]::Now.ToString());
            
            return "$server|testPing|$result|$dateTime"; # server | function | result | DateTime
        } 
        
        testPing -server $server -pingCount $pingCount;
    }) # end of add script

    $pingCount = "1";
    $PowerShell.AddParameter('server',$server).AddParameter('pingCount',$pingCount) | Out-Null;
    $returnVal = $PowerShell.BeginInvoke();
    $temp = "" | Select PowerShell,returnVal;
    $temp.PowerShell = $PowerShell;
    $temp.returnVal = $returnVal;
    $threads.Add($Temp) | Out-Null;
}


$completed = $false;
while ($completed -eq $false)
{
    $completed = $true;
    
    foreach ($thread in $threads)
    {
        $endInvoke = $thread.PowerShell.EndInvoke($thread.returnVal);
        $endInvoke;
        $threadHandle = $thread.returnVal.AsyncWaitHandle.Handle;
        $threadisCompleted = $thread.returnVal.IsCompleted;
        #Write-Host "$threadHandle is $threadisCompleted";
        if ($threadisCompleted -eq $false)
        {
            $completed = $false;
        }
    }
    Write-Host "";
    sleep -Milliseconds 500;
}

foreach ($thread in $threads)
{
    $thread.PowerShell.dispose();
}

$stopwatch.Stop();

Write-Host "";
Write-Host "Thread count:" $threads.Count;
Write-Host "Time elapsed:" $stopwatch.Elapsed.Seconds;

解决方法

以下是捕获返回值数据的方法。您使用 2 个属性名称定义自定义对象 $temp,在本例中为 Powershell 和 returnVal。然后将它们添加到数组列表中。异步 BeginInvoke 完成后,您可以通过执行此操作对 asyncResult 调用 EndInvoke $endInvoke = $thread.PowerShell.EndInvoke($thread.returnVal);并根据需要对其进行解析。

这一定是我写过的最复杂的脚本,其中包含运行空间池、运行空间、异步返回、传递的函数,甚至还混入了一些 c#。希望其他人可以从这些部分或全部中汲取经验。

$returnVal = $PowerShell.BeginInvoke();
$temp = "" | Select PowerShell,returnVal;
$temp.PowerShell = $PowerShell;
$temp.returnVal = $returnVal;
$threads.Add($Temp) | Out-Null;

$completed = $false;
while ($completed -eq $false)
{
    $completed = $true;
    
    foreach ($thread in $threads)
    {
        $endInvoke = $thread.PowerShell.EndInvoke($thread.returnVal);
        $endInvoke;
        $threadHandle = $thread.returnVal.AsyncWaitHandle.Handle;
        $threadIsCompleted = $thread.returnVal.IsCompleted;
        #Write-Host "$threadHandle is $threadIsCompleted";
        if ($threadIsCompleted -eq $false)
        {
            $completed = $false;
        }
    }
    Write-Host "";
    sleep -Milliseconds 500;
}

foreach ($thread in $threads)
{
    $thread.PowerShell.Dispose();
}