M1 Mac上.NET Process.Start执行bash脚本远慢于终端问题
.NET Process调用bash脚本在M1 Mac上耗时过长问题解决
问题背景
- 编写的bash脚本在终端执行耗时不足2秒,但通过.NET的Process.Start调用时,M1 Mac上耗时约30秒,Linux部署后执行正常。
- 脚本内容:
#!/bin/bash date >> test.db tac /Users/user/Desktop/catalina.out.2022-06-17-06H30M | grep [Info] | grep -m 1000 "Duration: " >> test.txt date >> test.db
- .NET调用代码:
DateTime start = DateTime.Now; Process myProc = new Process(); myProc.StartInfo.RedirectStandardOutput = true; myProc.StartInfo.FileName = "/opt/homebrew/bin/bash"; myProc.StartInfo.Arguments = $"-c \"/Users/user/Desktop/test.sh \""; myProc.Start(); this.result = await myProc.StandardOutput.ReadToEndAsync(); myProc.WaitForExit(); DateTime end = DateTime.Now; TimeSpan duration = end - start; this.duration = duration.TotalSeconds.ToString();
核心原因
M1 Mac上,.NET Process在处理非交互式bash进程的输出缓冲时存在阻塞:
- 脚本所有输出均写入文件,无stdout输出,但.NET仍开启了
RedirectStandardOutput并等待读取,触发不必要的缓冲等待。 - bash在非终端环境下默认使用块缓冲,而非终端的行缓冲,导致进程结束前缓冲区未刷新,.NET端持续等待。
解决方案
方案1:关闭不必要的输出重定向
脚本无stdout输出,无需开启RedirectStandardOutput,直接移除相关配置:
Process myProc = new Process(); // 移除 RedirectStandardOutput = true myProc.StartInfo.FileName = "/opt/homebrew/bin/bash"; myProc.StartInfo.Arguments = $"-c \"/Users/user/Desktop/test.sh\""; myProc.Start(); myProc.WaitForExit();
方案2:直接执行脚本而非通过bash -c
利用脚本的#!/bin/bash shebang,直接调用脚本文件,避免bash -c的额外处理:
Process myProc = new Process(); myProc.StartInfo.FileName = "/Users/user/Desktop/test.sh"; myProc.StartInfo.UseShellExecute = false; // 必须设置为false以直接执行可执行文件 myProc.Start(); myProc.WaitForExit();
方案3:强制bash无缓冲输出
使用stdbuf命令强制bash的stdout/stderr无缓冲,避免阻塞:
myProc.StartInfo.FileName = "/usr/bin/stdbuf"; myProc.StartInfo.Arguments = $"-o0 -e0 /opt/homebrew/bin/bash -c \"/Users/user/Desktop/test.sh\"";
-o0表示stdout无缓冲,-e0表示stderr无缓冲,确保进程输出立即刷新。
方案4:同时处理stdout和stderr(若需保留重定向)
如果必须开启输出重定向,需同时读取stdout和stderr,避免因stderr缓冲满导致进程阻塞:
Process myProc = new Process(); myProc.StartInfo.RedirectStandardOutput = true; myProc.StartInfo.RedirectStandardError = true; myProc.StartInfo.FileName = "/opt/homebrew/bin/bash"; myProc.StartInfo.Arguments = $"-c \"/Users/user/Desktop/test.sh\""; myProc.Start(); // 同时读取stdout和stderr,避免死锁 var readOutputTask = myProc.StandardOutput.ReadToEndAsync(); var readErrorTask = myProc.StandardError.ReadToEndAsync(); await Task.WhenAll(readOutputTask, readErrorTask); this.result = await readOutputTask; myProc.WaitForExit();
验证结果
以上方案均可解决M1 Mac上的耗时问题,使.NET调用脚本的速度与终端执行一致。
内容的提问来源于stack exchange,提问作者Marko
相关产品推荐
相关产品推荐

