using System; using System.Threading; namespace Gallery.Util { public class ParallelTask : IDisposable { public static ParallelTask Start(string tag, int from, int toExclusive, int maxCount, Predicate action, int tagIndex = -1, WaitCallback complete = null) { if (toExclusive <= from) { if (complete != null) { ThreadPool.QueueUserWorkItem(complete); } return null; } var task = new ParallelTask(tag, from, toExclusive, maxCount, action, tagIndex, complete); task.Start(); return task; } private readonly object sync = new(); private int count; private bool disposed; public int TagIndex { get; private set; } private readonly string tag; private readonly int max; private readonly int from; private readonly int to; private readonly Predicate action; private readonly WaitCallback complete; private ParallelTask(string tag, int from, int to, int maxCount, Predicate action, int tagIndex, WaitCallback complete) { if (maxCount <= 0) { throw new ArgumentOutOfRangeException(nameof(maxCount)); } max = maxCount; if (from >= to) { throw new ArgumentOutOfRangeException(nameof(from)); } TagIndex = tagIndex; this.tag = tag; this.from = from; this.to = to; this.action = action; this.complete = complete; } public void Start() { ThreadPool.QueueUserWorkItem(DoStart); } public void Dispose() { disposed = true; } private void DoStart(object state) { #if DEBUG const long TIMEOUT = 60000L; var sw = new System.Diagnostics.Stopwatch(); long lastElapsed = 0; sw.Start(); #endif for (int i = from; i < to; i++) { var index = i; while (count >= max) { #if DEBUG var elapsed = sw.ElapsedMilliseconds; if (elapsed - lastElapsed > TIMEOUT) { lastElapsed = elapsed; Log.Print($"WARNING: parallel task ({tag}), {count} tasks in queue, cost too much time ({elapsed:n0}ms)"); } #endif if (disposed) { #if DEBUG sw.Stop(); Log.Print($"parallel task determinate, disposed ({tag}), cost time ({elapsed:n0}ms)"); #endif return; } Thread.Sleep(16); } lock (sync) { count++; } ThreadPool.QueueUserWorkItem(o => { try { if (!action(index)) { disposed = true; } } catch (Exception ex) { Log.Error($"parallel.start ({tag})", $"failed to run action, index: {index}, error: {ex}"); } finally { lock (sync) { count--; } } }); } while (count > 0) { #if DEBUG var elapsed = sw.ElapsedMilliseconds; if (elapsed - lastElapsed > TIMEOUT) { lastElapsed = elapsed; Log.Print($"WARNING: parallel task ({tag}), {count} ending tasks in queue, cost too much time ({elapsed:n0}ms)"); } #endif if (disposed) { #if DEBUG sw.Stop(); Log.Print($"parallel task determinate, disposed ({tag}), ending cost time ({elapsed:n0}ms)"); #endif return; } Thread.Sleep(16); } #if DEBUG sw.Stop(); Log.Print($"parallel task done ({tag}), cost time ({sw.ElapsedMilliseconds:n0}ms)"); #endif complete?.Invoke(null); } } }