为何传递至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
相关产品推荐
相关产品推荐

