zoukankan      html  css  js  c++  java
  • PowerShell 中 RunspacePool 执行异步多线程任务

    在 PowerShell 中要执行任务脚本,现在通常使用 Runspace,效率很高;任务比较多时,用 Runspace pool 来执行异步操作,可以控制资源池数量,就像 C# 中的线程池一样

    ================================================

    为了对比,我们分别采用同步和异步(多线程)方式,模拟执行10个任务,并且每个任务都接收一个参数,执行完成后返回执行结果

    ================================================

    同步执行方法    输入参数 $toexecute 是一个任务脚本数组,方法内遍历任务脚本,直接通过脚本的 Invoke 方法执行(也可以创建一个 PowerShell 对象添加脚本,通过该 PowerShell 对象的 Invoke 方法执行),然后输出执行结果

    # 执行同步(单线程)任务
    function RunJob {
        param($toexecute)
        # 遍历执行所有脚本
        [int]$arg = 0
        foreach($s in $toexecute) {
            $result = $s.Invoke($arg++)   # 执行带参数的任务脚本
            # 执行结果返回一个含有 Success 属性的对象
            if ($result.Success) { 
                Write-Host (" -> 任务执行成功 " + $result.Data + ",当前线程 " + $result.ThreadId) -ForegroundColor Green
            } 
            else { 
                Write-Host (" -> 任务执行失败 " + $result.Data + ",当前线程 " + $result.ThreadId) -ForegroundColor Red
            }
        }
    }

    异步执行方法    输入参数 $toexecute 是一个任务脚本数组,方法内遍历任务脚本,

    通过 PowerShell 对象 $psl 添加执行脚本和参数,返回一个作业对象 $job
    通过 Runspace pool 对象 $rsp 控制异步多线程,
    通过 $job 的 BeginInvoke 方法提交异步操作,
    通过轮询等待所有作业执行完成(IsCompleted),
    通过 $job 的 EndInvoke 获得执行结果

    # 执行异步(多线程)任务
    function RunJobAsync {
        param($toexecute)
        $rsp = [RunspaceFactory]::CreateRunspacePool(1, 5)  #设置资源池中Runspace数量最少和最多
        $rsp.Open()
        $jobs = @()
        [int]$arg = 0
        # 遍历执行所有脚本
        foreach($s in $toexecute) {
            $psl = [Powershell]::Create()
            $job = $psl.AddScript($s).AddArgument($arg++)    # 添加任务脚本和参数
            $job.RunspacePool = $rsp         
            Write-Host $("添加任务... " + $job.InstanceId)
            $jobs += New-Object PSObject -Property @{ 
                Job = $job
                PowerShell = $psl
                Result = $job.BeginInvoke()  # 异步执行任务脚本
            }
        }
    
        # 轮询等待任务完成
        do 
        { 
            Start-Sleep -seconds 1
            $cnt = ($jobs | Where {$_.Result.IsCompleted -ne $true}).Count
            Write-Host ("运行中的任务数量: " + $cnt)
        } while ($cnt -gt 0)
    
        foreach($r in $jobs) {    
            Write-Host ("任务结果: " + $r.Job.InstanceId) 
            $result = $r.Job.EndInvoke($r.Result)   # 取得异步执行结果
            
            # 注销 PowerShell 对象
            $r.PowerShell.Dispose()
    
            # 输出完成的任务脚本 
            #Write-Output ($result) 
                    
            # 执行结果返回一个含有 Success 属性的对象
            if ($result.Success) { 
                Write-Host (" -> 任务执行成功 " + $result.Data + ",当前线程 " + $result.ThreadId) -ForegroundColor Green
            } 
            else { 
                Write-Host (" -> 任务执行失败 " + $result.Data + ",当前线程 " + $result.ThreadId) -ForegroundColor Red
            }
        }
    }

    初始化任务脚本,循环创建10个任务脚本,每个任务通过等待1秒钟模拟脚本执行,定义一个 PSObject 对象作为返回结果,其中属性 Success 代表是否成功(故意设置传入参数5时的任务失败),Data 代表执行结果,ThreadId 标识当前线程ID

    这种方式就像 C# 中的方法委托一样(PowerShell 使用了大量.NET类库),在调用端用委托定义执行过程和结果,然后将委托以变量形式传递给执行端

    $toexecute = @()  # 任务脚本列表
    foreach($i in 1..10) {
        $toexecute += {
            param($state)  #可接收参数
            Start-Sleep -Seconds 1
            New-Object PSObject -Property @{ 
                Success = $state -ne 5       # 假设传入参数5时失败,其余成功
                Data = "结果 $state"     # 假设Data是执行结果,带上传入参数以区分
                ThreadId = [AppDomain]::GetCurrentThreadId()   # 当前线程ID  
            }
        }
    }

    注:PowerShell 对象 AddScript 加载脚本执行,也可以传入一个脚本文件路径,因此每个任务脚本可以写到单独的 .ps1 文件中

    ============================================================================

    下面调用同步方法 RunJob,并且测量执行时间

    Clear-Host
    $watch = Measure-Command {
        RunJob -toexecute $toexecute
    }
    $elapsed = [Math]::Round($watch.TotalMilliseconds / 1000.0, 2)
    Write-Output ("同步执行 "+ $toexecute.Count +" 个任务耗时" + $elapsed + "")

    不出所料,在1个线程 20512 中执行10个任务,耗时10.06秒

    ============================================================================

    下面调用异步方法 RunJobAsync,并且测量执行时间

    Clear-Host
    $watch = Measure-Command {
        RunJobAsync -toexecute $toexecute
    }
    $elapsed = [Math]::Round($watch.TotalMilliseconds / 1000.0, 2)
    Write-Output ("异步执行 "+ $toexecute.Count +" 个任务耗时" + $elapsed + "")

    执行结果如下图,在5个线程中执行10个任务(差不多每个线程执行2个任务),耗时仅2.15秒

    如果我们将代码中 [RunspaceFactory]::CreateRunspacePool(1, 5) 中最大资源数改为10,基本每个任务都能有1个线程执行,测试耗时就1秒多一点点

    ============================================================================

    写得有点乱,就当是笔记了

    参考资料

    PowerShell runspace 的创建,使用和查错

    Multithreading with PowerShell using RunspacePool

  • 相关阅读:
    Android 从上层到底层-----kernel层
    同时支持来自多个源头的域名的跨域调用
    Redis
    很好用的工具网站
    PHP array_combine()
    php 统计某个目录中所有文件的大小
    strchr()
    PHP $_SERVER
    Laravel5使用QQ邮箱发送邮件配置
    laravel 5.6
  • 原文地址:https://www.cnblogs.com/felixnet/p/10772146.html
Copyright © 2011-2022 走看看