TaskUtils.cs 2.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
  1. using System.Diagnostics;
  2. namespace MeloongCore;
  3. public static class TaskUtils {
  4. public static void Run(this Task task) => task.GetAwaiter().GetResult();
  5. public static T Run<T>(this Task<T> task) => task.GetAwaiter().GetResult();
  6. /// <summary>
  7. /// 静默运行程序并等待其结束,返回其输出和退出码。
  8. /// 支持 notepad、git 等命令。
  9. /// 超时或启动失败会抛出异常。
  10. /// </summary>
  11. public static async Task<(string Output, int ExitCode)> RunProgramAsync(string file, string arguments = "", int? timeoutMs = null, Encoding? encoding = null) {
  12. var info = new ProcessStartInfo {
  13. Arguments = arguments,
  14. FileName = PathUtils.ToShortPath(file),
  15. UseShellExecute = false,
  16. CreateNoWindow = true,
  17. RedirectStandardOutput = true, StandardOutputEncoding = encoding,
  18. RedirectStandardError = true, StandardErrorEncoding = encoding
  19. };
  20. using var program = new Process { StartInfo = info, EnableRaisingEvents = true };
  21. if (!program.Start()) throw new InvalidOperationException($"运行程序时出现意外错误:{file} {arguments}");
  22. bool hasTimeout = timeoutMs.HasValue && timeoutMs > 0;
  23. Logger.Info($"运行程序,并返回其输出:{file} {arguments}{(hasTimeout ? $",最长可等待 {timeoutMs}ms" : "")}");
  24. // 读取输出和错误流
  25. var outputTask = program.StandardOutput.ReadToEndAsync();
  26. var errorTask = program.StandardError.ReadToEndAsync();
  27. var completionSource = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
  28. program.Exited += (_, _) => completionSource.TrySetResult(null);
  29. if (program.HasExited) completionSource.TrySetResult(null);
  30. var task = completionSource.Task;
  31. // 等待超时
  32. if (hasTimeout) {
  33. Logger.Trace($"等待程序 {program.Id} 完成");
  34. if (await Task.WhenAny(task, Task.Delay(timeoutMs!.Value)) != task) {
  35. try {
  36. if (!program.HasExited) program.Kill();
  37. } catch (InvalidOperationException) { // 进程已退出,无需处理
  38. }
  39. throw new TimeoutException($"运行程序超时:{file} {arguments}");
  40. }
  41. }
  42. await task;
  43. return (await outputTask + await errorTask, program.ExitCode);
  44. }
  45. }