共计 2963 个字符,预计需要花费 8 分钟才能阅读完成。
真实场景需求
最近在开发一个视频处理服务时,需要调用同目录下的 FFmpeg.exe 进行转码操作。直接使用 Process.Start("ffmpeg.exe") 时,发现开发环境能运行但发布后总报错——原来是因为工作目录变化导致找不到 exe 文件。这让我意识到正确处理路径和进程调用的重要性。

基础调用 vs 封装工具类
直接使用 Process.Start 的优缺点:
– 优点:代码简单,适合快速测试
– 缺点:
– 缺乏超时控制
– 难以捕获实时输出
– 路径处理脆弱
封装工具类的价值:
– 统一路径解析逻辑
– 内置异常处理和资源释放
– 支持异步和取消操作
核心实现方案
1. 获取程序集所在目录的三种方法
// 方法 1:传统方案(不适用于所有场景)string path1 = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
// 方法 2:兼容 Docker 容器内路径
string path2 = AppContext.BaseDirectory;
// 方法 3:处理符号链接等特殊情况
string path3 = Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName);
2. 带超时控制的进程调用
using var process = new Process();
process.StartInfo = new ProcessStartInfo
{
FileName = "ffmpeg.exe",
WorkingDirectory = GetExecutableDir(), //< 使用上述方法获取的目录 >
UseShellExecute = false,
CreateNoWindow = true
};
var cancellationSource = new CancellationTokenSource(TimeSpan.FromSeconds(30));
try
{process.Start();
await process.WaitForExitAsync(cancellationSource.Token);
}
catch (OperationCanceledException)
{process.Kill();
throw new TimeoutException("进程执行超时");
}
3. 实时输出捕获
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
var outputBuilder = new StringBuilder();
process.OutputDataReceived += (sender, e) =>
{if (!string.IsNullOrEmpty(e.Data))
{outputBuilder.AppendLine(e.Data);
Console.WriteLine($"[OUT] {e.Data}"); //< 实时打印到控制台 >
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
生产环境关键问题
权限问题排查清单
- Windows 环境:
- 检查 UAC 虚拟化是否重定向了文件写入
- 管理员权限下测试是否解决问题
- Linux 环境:
- 确保 exe 有可执行权限(chmod +x)
- 考虑使用 sudoers 文件配置免密 sudo
杀毒软件误报处理
- 将工具 exe 加入杀毒软件白名单
- 对辅助 exe 进行代码签名
- 准备备用方案(如调用系统内置工具)
进程残留处理
// 强制终止进程树
public static void KillProcessTree(Process process)
{foreach (var child in new ManagementObjectSearcher($"Select * From Win32_Process Where ParentProcessID={process.Id}").Get())
{using (var mo = child as ManagementObject)
{KillProcessTree(Process.GetProcessById(Convert.ToInt32(mo["ProcessID"])));
}
}
process.Kill();}
完整工具类实现
public sealed class ProcessExecutor : IDisposable
{
private Process _process;
private readonly StringBuilder _output = new();
public async Task<string> ExecuteAsync(
string exeName,
string arguments,
TimeSpan timeout,
CancellationToken cancellationToken = default)
{
_process = new Process
{
StartInfo = new ProcessStartInfo
{FileName = Path.Combine(GetExecutableDir(), exeName),
Arguments = arguments,
//... 其他配置
}
};
using var timeoutTokenSource = new CancellationTokenSource(timeout);
using var linkedTokenSource = CancellationTokenSource.CreateLinkedTokenSource(
cancellationToken,
timeoutTokenSource.Token);
try
{_process.Start();
return await _process.WaitForExitCaptureOutputAsync(linkedTokenSource.Token);
}
finally
{if (!_process.HasExited)
KillProcessTree(_process);
}
}
public void Dispose() => _process?.Dispose();
}
单元测试示例
[Fact]
public async Task Should_Timeout_When_ProcessHangs()
{using var executor = new ProcessExecutor();
await Assert.ThrowsAsync<TimeoutException>(() =>
executor.ExecuteAsync("test.exe", "hang", TimeSpan.FromSeconds(1)));
}
实践总结
通过这次完整的工具封装,我们解决了以下典型问题:
1. 发布后路径错误导致的 exe 找不到
2. 长时间卡死的进程无法自动终止
3. 输出日志无法实时查看
4. 权限问题缺乏明确提示
建议在工具类基础上根据业务需求扩展:
– 添加进程资源占用监控
– 支持白名单校验 exe 哈希值
– 实现自动重试机制
这套方案已在生产环境稳定运行半年,日均调用量超过 2 万次。关键点在于正确处理路径和做好异常防御,希望本文的经验对你有帮助。
正文完
