使用Semaphore和Register-ObjectEvent限制ffmpeg进程数遇异常求助
问题排查:PowerShell限制ffmpeg并发进程停滞问题
问题描述
我编写了一段PowerShell代码,想要通过Semaphore和Register-ObjectEvent将同时运行的ffmpeg进程数限制为2,实现批量视频转码处理。但运行后仅前2个文件被处理就停滞了,控制台显示ffmpeg未退出,可在另一个PowerShell会话中查询不到任何ffmpeg进程。需要排查代码逻辑问题,找出根源。
原代码
$working_dir = "C:\temp\video\test" $input_files = @(Get-ChildItem -Path $working_dir -Include @("*.webm", "*.mkv") -Recurse -File) $throttleLimit = 2 $semaphore = [System.Threading.Semaphore]::new($throttleLimit, $throttleLimit) $jobs = @() foreach ($v in $input_files) { $new_name = [regex]::replace($v.fullname, '\.[^\.]+$', '') + ".mp4" $ArgumentList = "-i `"$($v.fullname)`" -metadata comment= -metadata title= -filter_complex `"drawtext=fontsize=10:fontfile='I\:\temp\sarasa-term-sc-bold.ttf':text='':x=228:y=840:fontcolor=000000`" -c:a copy -y `"$($new_name)`"" $semaphore.WaitOne() $process = Start-Process -FilePath "ffmpeg.exe" -ArgumentList $ArgumentList -PassThru -NoNewWindow $job = Register-ObjectEvent -InputObject $process -EventName "Exited" -Action { $semaphore.Release() Unregister-Event $eventSubscriber.SourceIdentifier Remove-Job $eventSubscriber.Action } $jobs += $job } $jobs | Wait-Job | Receive-Job
进程排查结果
PS C:\WINDOWS\system32> ps ffmpeg ps : Cannot find a process with the name "ffmpeg". Verify the process name and call the cmdlet again. At line:1 char:1 + ps ffmpeg + ~~~~~~~~~ + CategoryInfo : ObjectNotFound: (ffmpeg:String) [Get-Process], ProcessCommandException + FullyQualifiedErrorId : NoProcessFoundForGivenName,Microsoft.PowerShell.Commands.GetProcessCommand PS C:\WINDOWS\system32> ps *ffmpeg* PS C:\WINDOWS\system32>
问题根源分析
- 信号量作用域隔离:
$semaphore是在主脚本作用域定义的,但Register-ObjectEvent的Action块运行在独立的Job作用域中,默认无法访问主作用域的变量。这导致ffmpeg进程退出后,Action里的$semaphore.Release()根本执行不了,信号量一直被占用,后续文件无法获取信号量启动新进程。 - ffmpeg参数传递错误:你构建的
$ArgumentList使用了转义的",这种字符串转义方式会导致ffmpeg解析参数出错,进程可能启动后立刻异常退出,但主脚本中的$process对象没正确触发Exited事件,进一步加剧了信号量无法释放的问题。 - 进程事件未启用:
Start-Process返回的Process对象默认未开启事件触发,即便进程正常退出,也不会触发Exited事件,导致Action块无法执行。
解决方法及修正代码
核心修复点
- 通过
-MessageData传递信号量:将$semaphore通过Register-ObjectEvent的-MessageData参数传入Action块,在块内通过$event.MessageData访问。 - 改用参数数组传递ffmpeg参数:避免字符串转义错误,让
Start-Process正确处理每个参数。 - 启用进程事件触发:手动设置
$process.EnableRaisingEvents = $true,确保进程退出时触发Exited事件。
修正后的完整代码
$working_dir = "C:\temp\video\test" $input_files = @(Get-ChildItem -Path $working_dir -Include @("*.webm", "*.mkv") -Recurse -File) $throttleLimit = 2 $semaphore = [System.Threading.Semaphore]::new($throttleLimit, $throttleLimit) $jobs = @() foreach ($v in $input_files) { $new_name = [regex]::replace($v.fullname, '\.[^\.]+$', '') + ".mp4" # 改用参数数组传递,避免转义问题 $ArgumentList = @( "-i", $v.FullName, "-metadata", "comment=", "-metadata", "title=", "-filter_complex", "drawtext=fontsize=10:fontfile='I:\temp\sarasa-term-sc-bold.ttf':text='':x=228:y=840:fontcolor=000000", "-c:a", "copy", "-y", $new_name ) $semaphore.WaitOne() $process = Start-Process -FilePath "ffmpeg.exe" -ArgumentList $ArgumentList -PassThru -NoNewWindow # 必须开启事件触发,否则Exited事件不会触发 $process.EnableRaisingEvents = $true $job = Register-ObjectEvent -InputObject $process -EventName "Exited" -MessageData $semaphore -Action { # 通过MessageData访问主作用域的信号量 $event.MessageData.Release() # 清理事件订阅和Job资源 Unregister-Event $eventSubscriber.SourceIdentifier Remove-Job $eventSubscriber.Action } $jobs += $job } # 等待所有事件处理Job完成 $jobs | Wait-Job | Receive-Job # 最后释放信号量资源 $semaphore.Dispose()
内容的提问来源于stack exchange,提问作者Fajela Tajkiya
相关产品推荐
相关产品推荐

