停止PowerShell时,.NET方法的赋值操作是否具备原子性?
背景信息
原子性的工作定义
针对本问题,适用以下“原子性”定义:
给定运行空间中执行的一条语句:
a. 包含一个调用.NET方法生成新对象的表达式;
b. 引用了该语句作用域内可访问的PowerShell变量;
c. 该语句是将(a)中.NET方法返回的对象赋值给(b)中的PowerShell变量;
d. 若运行空间未被停止,该语句及赋值操作可无错误或异常执行完成;当且仅当对于所有可能的组合:
e. 触发运行空间停止的时间点;
f. 符合C#或.NET标准规范的.NET方法有效实现;满足以下任一情况时,该语句具备原子性:
g. .NET方法未开始执行;
h. .NET方法完整执行完毕,且返回的对象被赋值给目标变量。
另有补充定义可提升清晰度:
……假设右侧表达式已执行完成,且赋值给目标变量未违反类型约束,Stop-Job是否会阻碍赋值操作?
不具备原子性的赋值表达式
构造一个对象已创建但赋值未完成的语句十分简单,如下例(看似有些反常):
$fs = @([System.IO.StreamWriter]::new($filePath) Start-Sleep 1)
此场景下,作业可能在第一个StreamWriter未被释放时就被停止,后续第二个new()会执行失败并抛出错误:
Exception calling ".ctor" with "1" argument(s): "The process cannot access the file 'C:\Users\User\AppData\Local\Temp\tmpzc3tf0.tmp' because it is being used by another process."
但其中显眼的Start-Sleep 1可替换为任何合法的实用操作。另一个易受此问题影响的场景是构造函数被封装在New-StreamWriter这类高级函数中。引入高级函数是否会导致其调用的.NET方法赋值失去原子性?
测试结果
我准备了以下测试,用于检测不同表达式的赋值是否不具备原子性。但即使赋值实际不具备原子性,测试也可能因时机巧合而通过。因此该测试只能发现问题,无法证明无问题。
测试输出如下:
result assignment use_again_error ------ ---------- --------------- - $fs = [System.IO.StreamWriter]::new($filePath) $c.Set() - $fs = New-Object System.IO.StreamWriter $filePath $c.Set() ❌ $fs = Exception calling ".ctor" with "1" argument(s): "The @([System.IO.StreamWriter]::new($filePath) process cannot access the file $c.Set() 'C:\Users\User1\AppData\Local\Temp\tmpgm0d0q.tmp' because Start-Sleep 1) it is being used by another process." - $fs = New-StreamWriter $filePath
测试代码
{$fs = [System.IO.StreamWriter]::new($filePath) $c.Set() } , {$fs = New-Object System.IO.StreamWriter $filePath $c.Set() } , {$fs = @([System.IO.StreamWriter]::new($filePath) $c.Set() Start-Sleep 1) } , {$fs = New-StreamWriter $filePath} | ForEach-Object { $assignment = $_ $filePath = [system.io.path]::GetTempFileName() $c = [System.Threading.ManualResetEventSlim]::new() # 标记构造函数已执行完成 $job = Start-ThreadJob ` -ArgumentList $filePath,$assignment,$c ` -ScriptBlock { param($filePath,$assignment,$c) begin { function New-StreamWriter { param($filePath) [System.IO.StreamWriter]::new($filePath) $c.Set() } } end { . ($assignment.Ast.GetScriptBlock()) } clean { ${fs}?.Dispose() } } $c.Wait() | Out-Null $assignment_error = @( $job.Error try { $job | Receive-Job -ea Stop } catch { $_ } ) | Select-Object -Unique $use_again_error = $(try { $fs2 = [System.IO.StreamWriter]::new($filePath)} catch {$_}) [pscustomobject]@{ result = $(if($assignment_error) {'⚠️'} else {if($use_again_error) {'❌'} else {''}}) assignment = $assignment assignment_error = $assignment_error use_again_error = $use_again_error } ${fs2}?.Dispose() } | Format-Table ` -Wrap ` -Property @( @{ Name = 'result';Expression = 'result'; Width = 'result'.Length } @{ Name = 'assignment';Expression = 'assignment'; Width = 50 } @{ Name = 'use_again_error';Expression = 'use_again_error'; Width = 60 } )
内容的提问来源于stack exchange,提问作者alx9r
相关产品推荐
相关产品推荐

