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

使用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块无法执行。

解决方法及修正代码

核心修复点

  1. 通过-MessageData传递信号量:将$semaphore通过Register-ObjectEvent的-MessageData参数传入Action块,在块内通过$event.MessageData访问。
  2. 改用参数数组传递ffmpeg参数:避免字符串转义错误,让Start-Process正确处理每个参数。
  3. 启用进程事件触发:手动设置$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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.20 02:45:38