C#异步启动外部程序并等待退出获取结果

` public static async Task RunAsync(string workingPath, string programFileName, string arguments,
int timeOutOfSeconds, string timeOutMsg, bool hidden, bool fetchMsg = true,
CancellationToken cancellationToken = default)
{
if (timeOutOfSeconds <= 0) return string.Empty;

var startInfo = new ProcessStartInfo
{
FileName = Path.Combine(workingPath, programFileName),
WindowStyle = hidden ? ProcessWindowStyle.Hidden : ProcessWindowStyle.Normal,
CreateNoWindow = true,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
WorkingDirectory = workingPath,
Arguments = arguments,
};

using (var process = new Process())
{
process.StartInfo = startInfo;
process.EnableRaisingEvents = true;

var tcs = new TaskCompletionSource();

process.Exited += (sender, args) => { Task.Run(() => tcs.TrySetResult(true)); };

process.Start();

try
{
process.PriorityClass = ProcessPriorityClass.AboveNormal;
}
catch

var readOutputTask = process.StandardOutput.ReadToEndAsync();
var readErrorTask = process.StandardError.ReadToEndAsync();

using (var timeoutCts = new CancellationTokenSource(timeOutOfSeconds * 1000))
using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, timeoutCts.Token))
{
// 当取消发生时,杀掉进程(这会触发 Exited 事件,从而让 tcs 完成)
using (linkedCts.Token.Register(() => TryKillProcess(process)))
{
try
{
var task = await Task.WhenAny(tcs.Task, Task.Delay(Timeout.Infinite, linkedCts.Token));
if (task.IsCanceled)
{
linkedCts.Token.ThrowIfCancellationRequested();
}
}
catch (OperationCanceledException)
{
// --- 超时或外部取消触发 ---

// 进程已被 Register 回调 Kill,等待读取流任务结束(进程被杀后流会自动关闭并抛出异常/返回残余数据)
await FlushStreamsAsync(readOutputTask, readErrorTask);

// 判断到底是超时还是外部取消
if (timeoutCts.IsCancellationRequested && !cancellationToken.IsCancellationRequested)
{
return timeOutMsg; // 超时
}
else
{
throw;
}
}
finally
{
// 兜底确保进程已死
TryKillProcess(process);
}
}
}

// --- 正常退出逻辑 ---

// 确保所有流都正常读取完毕
await FlushStreamsAsync(readOutputTask, readErrorTask);

string msg = await readOutputTask;
string error = await readErrorTask; // 不要丢弃错误信息

if (!string.IsNullOrWhiteSpace(msg))
{
if (fetchMsg) return msg;

WriteToDebugLogFile(
"CallConsoleProgram programFileName={0}, args={1}, StandardOutput={2}",
programFileName, arguments, msg);
return string.Empty;
}

// 如果标准输出为空,但标准错误有内容,通常也应该记录或抛出异常
if (!string.IsNullOrWhiteSpace(error))
{
WriteToDebugLogFile(
"CallConsoleProgram programFileName={0}, args={1}, StandardError={2}",
programFileName, arguments, error);
}

return string.Empty;
}
}

// 提取为独立方法,传入具体的 Task,方便处理异常和获取已读取的数据
private static async Task FlushStreamsAsync(Task readOutputTask, Task readErrorTask)
{
try
{
await Task.WhenAll(readOutputTask, readErrorTask).ConfigureAwait(false);
}
catch
{
// 进程被杀后流读取失败是预期行为,直接丢弃异常
// 但注意:此时 readOutputTask.Result 可能包含进程死前输出的部分内容!
}
}

// 辅助方法:安全杀进程
private static void TryKillProcess(Process process)
{
try
{
if (!process.HasExited)
{
process.Kill();
process.WaitForExit();
}
}
catch
{
// 进程可能刚好自行退出,忽略
}
}`

posted @ 2026-05-25 11:49  顺风使舵  阅读(12)  评论(0)    收藏  举报