2
0

TaskUtils.cs 5.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. using System.Diagnostics;
  2. namespace MeloongCore;
  3. public static class TaskUtils {
  4. /// <summary>
  5. /// 并行执行所有任务。
  6. /// </summary>
  7. public static Task WhenAll(IEnumerable<Func<ProgressProvider?, Task>> tasks, ProgressProvider? progress = null) {
  8. var taskList = tasks.ToList();
  9. if (!taskList.Any()) {
  10. progress?.Skip();
  11. return Task.CompletedTask;
  12. }
  13. // 实际分割
  14. var progressEach = 1d / taskList.Count;
  15. return Task.WhenAll(taskList.Select(async (task, _) => {
  16. var cp = progress?.SplitBy(progressEach)[0];
  17. await task(cp).NoCapture();
  18. cp?.Finish();
  19. }));
  20. }
  21. /// <summary>
  22. /// 并行执行所有任务并返回结果。
  23. /// </summary>
  24. public static Task<TResult[]> WhenAll<TResult>(IEnumerable<Func<ProgressProvider?, Task<TResult>>> tasks, ProgressProvider? progress = null) {
  25. var taskList = tasks.ToList();
  26. if (!taskList.Any()) {
  27. progress?.Skip();
  28. return Task.FromResult(Array.Empty<TResult>());
  29. }
  30. // 实际分割
  31. var progressEach = 1d / taskList.Count;
  32. return Task.WhenAll(taskList.Select(async (task, _) => {
  33. var cp = progress?.SplitBy(progressEach)[0];
  34. var result = await task(cp).NoCapture();
  35. cp?.Finish();
  36. return result;
  37. }));
  38. }
  39. /// <summary>
  40. /// 并行执行所有任务。
  41. /// </summary>
  42. public static Task WhenAll(IEnumerable<Task> tasks, ProgressProvider? progress = null)
  43. => WhenAll(tasks.Select(task => new Func<ProgressProvider?, Task>(_ => task)), progress);
  44. /// <summary>
  45. /// 并行执行所有任务并返回结果。
  46. /// </summary>
  47. public static Task<TResult[]> WhenAll<TResult>(IEnumerable<Task<TResult>> tasks, ProgressProvider? progress = null)
  48. => WhenAll(tasks.Select(task => new Func<ProgressProvider?, Task<TResult>>(_ => task)), progress);
  49. public static void ForEach<T>(IEnumerable<T> source, Action<T> body)
  50. => Parallel.ForEach(source, body);
  51. public static async Task ForEachAsync<T>(IEnumerable<T> source, int maxDegreeOfParallelism, Func<T, ProgressProvider?, Task> body,
  52. CancellationToken cancellationToken = default, ProgressProvider? progress = null) {
  53. using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
  54. var token = linkedCancellationTokenSource.Token;
  55. var enumeratorLock = new object();
  56. using var enumerator = source.GetEnumerator();
  57. async Task WorkerAsync(ProgressProvider? cp) {
  58. try {
  59. while (true) {
  60. T item;
  61. lock (enumeratorLock) {
  62. token.ThrowIfCancellationRequested();
  63. if (!enumerator.MoveNext()) return;
  64. item = enumerator.Current;
  65. }
  66. await body(item, cp).NoCapture();
  67. }
  68. } catch {
  69. try {
  70. linkedCancellationTokenSource.Cancel(); // 让尚未开始的 worker 尽快停止
  71. } catch {
  72. }
  73. throw;
  74. }
  75. }
  76. await TaskUtils.WhenAll(Enumerable.Repeat(WorkerAsync, maxDegreeOfParallelism), progress).NoCapture();
  77. }
  78. /// <summary>
  79. /// 静默运行程序并等待其结束,返回其输出和退出码。
  80. /// 支持 notepad、git 等命令。
  81. /// 超时或启动失败会抛出异常。
  82. /// </summary>
  83. public static async Task<(string Output, int ExitCode)> RunProgramAsync(string file, string arguments = "", int? timeoutMs = null, Encoding? encoding = null, CancellationToken cancellationToken = default) {
  84. cancellationToken.ThrowIfCancellationRequested();
  85. var info = new ProcessStartInfo {
  86. Arguments = arguments,
  87. FileName = PathUtils.ToShortPath(file),
  88. UseShellExecute = false,
  89. CreateNoWindow = true,
  90. RedirectStandardOutput = true, StandardOutputEncoding = encoding,
  91. RedirectStandardError = true, StandardErrorEncoding = encoding
  92. };
  93. using var program = new Process();
  94. program.StartInfo = info;
  95. program.EnableRaisingEvents = true;
  96. if (!program.Start()) throw new InvalidOperationException($"运行程序时出现意外错误:{file} {arguments}");
  97. bool hasTimeout = timeoutMs is > 0;
  98. Logger.Info($"运行程序,并返回其输出:{file} {arguments}{(hasTimeout ? $",最长可等待 {timeoutMs}ms" : "")}");
  99. // 输出和错误流
  100. var outputTask = program.StandardOutput.ReadToEndAsync();
  101. var errorTask = program.StandardError.ReadToEndAsync();
  102. var completionSource = new TaskCompletionSource<object?>(TaskCreationOptions.RunContinuationsAsynchronously);
  103. program.Exited += (_, _) => completionSource.TrySetResult(null);
  104. if (program.HasExited) completionSource.TrySetResult(null);
  105. var task = completionSource.Task;
  106. var timeoutTask = Task.Delay(hasTimeout ? timeoutMs!.Value : Timeout.Infinite);
  107. var cancelTask = Task.Delay(Timeout.Infinite, cancellationToken);
  108. Logger.Trace($"等待程序 {program.Id} 完成");
  109. var completedTask = await Task.WhenAny(task, timeoutTask, cancelTask).NoCapture();
  110. if (completedTask != task) {
  111. try {
  112. if (!program.HasExited) program.Kill();
  113. } catch (InvalidOperationException) { // 进程已退出,无需处理
  114. }
  115. if (completedTask == cancelTask) throw new OperationCanceledException(cancellationToken);
  116. throw new TimeoutException($"运行程序超时:{file} {arguments}");
  117. }
  118. return (await outputTask.NoCapture() + await errorTask.NoCapture(), program.ExitCode);
  119. }
  120. }