You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何传递至ScriptBlock的参数会被转换为字符串?

问题:无法将StreamWriter对象传递给Start-Job的ScriptBlock

我想把$writer变量原样传递到ScriptBlock里,让脚本块内部能向流写入内容,但遇到了以下错误:

Cannot process argument transformation on parameter 'sw'. Cannot convert value "[System.IO.StreamWriter]System.IO.StreamWriter" to type "System.IO.StreamWriter". Error: "Access to the path 
'C:\windows\system32\[System.IO.StreamWriter]System.IO.StreamWriter' is denied."
    + CategoryInfo          : InvalidData: (:) [], ParameterBindin...mationException
    + FullyQualifiedErrorId : ParameterArgumentTransformationError
    + PSComputerName        : localhost

看起来$writer被转换成了字符串,我试过多种强制转换组合都没用,我的代码如下:

$reader = [System.IO.File]::OpenText('C:\users\<removed>\downloads\queryfile-example-current\test.tsv')
$writer = New-Object System.IO.StreamWriter 'C:\users\<removed>\downloads\queryfile-example-current\test3.tsv'
for(;;) {
    $line = $reader.ReadLine()
    if ($null -eq $line) {
        break
    }
    Start-Job -ScriptBlock {
        param($record, $sw)
        $data = $record.Split("`t")

        #write-host "$($data[0] + ";" + $data[1])"
        Resolve-DnsName -Name $data[0] -Type $data[1] -DnsOnly
        $sw.WriteLine("{0}`t{1}", $data[0], $data[1])
    } -ArgumentList $line, [System.IO.StreamWriter]$writer | Out-Null

    Get-Job -State Completed | Receive-Job # -ErrorAction SilentlyContinue
    Get-Job -State Completed -HasMoreData $false | Remove-Job
}
$reader.Close()
$writer.Close()

While (Get-Job) {
    foreach ($job in get-job | where-object { $_.State -eq "Completed" -or $_.State -eq "Failed"})
    {
        if ($job.HasMoreData) {
            Receive-Job $job
        }
        elseif ($job.State -eq "Failed") {
            Receive-Job $job
        }
        else {
            Remove-Job $job
        }
    }
    sleep -seconds 1
}

原因分析

Start-Job创建的是独立的PowerShell进程,传递给Job的参数需要经过序列化/反序列化处理。而StreamWriter这类IO对象无法被序列化,传递时会被转换成字符串表示,导致Job里尝试把字符串当作StreamWriter使用时出错。

解决办法

方法1:Job返回结果,主进程统一写入文件

不在Job里直接写文件,而是让Job返回需要写入的数据,主进程收到后再写入StreamWriter:

$reader = [System.IO.File]::OpenText('C:\users\<removed>\downloads\queryfile-example-current\test.tsv')
$writer = New-Object System.IO.StreamWriter 'C:\users\<removed>\downloads\queryfile-example-current\test3.tsv'
for(;;) {
    $line = $reader.ReadLine()
    if ($null -eq $line) {
        break
    }
    Start-Job -ScriptBlock {
        param($record)
        $data = $record.Split("`t")
        # 执行DNS解析
        Resolve-DnsName -Name $data[0] -Type $data[1] -DnsOnly
        # 返回需要写入的内容
        return "{0}`t{1}" -f $data[0], $data[1]
    } -ArgumentList $line | Out-Null

    # 处理完成的Job,获取结果并写入文件
    foreach ($completedJob in Get-Job -State Completed) {
        $result = Receive-Job $completedJob
        $writer.WriteLine($result)
        Remove-Job $completedJob
    }
}
$reader.Close()
$writer.Close()

# 处理剩余的Job
While (Get-Job) {
    foreach ($job in Get-Job | Where-Object { $_.State -eq "Completed" -or $_.State -eq "Failed"}) {
        if ($job.HasMoreData -or $job.State -eq "Failed") {
            $result = Receive-Job $job
            if ($job.State -eq "Completed") {
                $writer.WriteLine($result)
            }
        }
        Remove-Job $job
    }
    Start-Sleep -Seconds 1
}

方法2:使用Runspace(同一进程内的线程,支持对象共享)

如果必须在后台任务中直接操作StreamWriter,可以用Runspace代替Start-Job(Runspace是同一进程的线程,能共享对象,但要注意线程安全,需要加锁):

$reader = [System.IO.File]::OpenText('C:\users\<removed>\downloads\queryfile-example-current\test.tsv')
$writer = New-Object System.IO.StreamWriter 'C:\users\<removed>\downloads\queryfile-example-current\test3.tsv'
$lock = New-Object System.Threading.Mutex

# 创建Runspace池
$runspacePool = [System.Management.Automation.Runspaces.RunspaceFactory]::CreateRunspacePool(1, 5)
$runspacePool.Open()

$jobs = @()

for(;;) {
    $line = $reader.ReadLine()
    if ($null -eq $line) {
        break
    }

    $ps = [System.Management.Automation.PowerShell]::Create()
    $ps.RunspacePool = $runspacePool
    $ps.AddScript({
        param($record, $writer, $lock)
        $data = $record.Split("`t")
        Resolve-DnsName -Name $data[0] -Type $data[1] -DnsOnly
        # 加锁防止多线程写入冲突
        $lock.WaitOne()
        try {
            $writer.WriteLine("{0}`t{1}", $data[0], $data[1])
        }
        finally {
            $lock.ReleaseMutex()
        }
    }).AddArgument($line).AddArgument($writer).AddArgument($lock)

    $jobs += @{
        PowerShell = $ps
        AsyncResult = $ps.BeginInvoke()
    }
}

# 等待所有Runspace完成并清理
foreach ($job in $jobs) {
    $job.PowerShell.EndInvoke($job.AsyncResult)
    $job.PowerShell.Dispose()
}

$runspacePool.Close()
$runspacePool.Dispose()
$reader.Close()
$writer.Close()
$lock.Dispose()

内容的提问来源于stack exchange,提问作者Appleoddity

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 04:17:17