using System; using System.Diagnostics.CodeAnalysis; using System.Threading; using System.Threading.Tasks; namespace DHT.Utils.Tasks; public sealed class AsyncValueComputer { private readonly Action resultProcessor; private readonly TaskScheduler resultTaskScheduler; private readonly bool processOutdatedResults; private readonly object stateLock = new (); private SoftHardCancellationToken? currentCancellationTokenSource; private bool wasHardCancelled = false; private Func? currentComputeFunction; private bool hasComputeFunctionChanged = false; private AsyncValueComputer(Action resultProcessor, TaskScheduler resultTaskScheduler, bool processOutdatedResults) { this.resultProcessor = resultProcessor; this.resultTaskScheduler = resultTaskScheduler; this.processOutdatedResults = processOutdatedResults; } public void Cancel() { lock (stateLock) { wasHardCancelled = true; currentCancellationTokenSource?.RequestHardCancellation(); } } public void Compute(Func func) { lock (stateLock) { wasHardCancelled = false; if (currentComputeFunction != null) { currentComputeFunction = func; hasComputeFunctionChanged = true; currentCancellationTokenSource?.RequestSoftCancellation(); } else { EnqueueComputation(func); } } } [SuppressMessage("ReSharper", "MethodSupportsCancellation")] private void EnqueueComputation(Func func) { var cancellationTokenSource = new SoftHardCancellationToken(); currentCancellationTokenSource?.RequestSoftCancellation(); currentCancellationTokenSource = cancellationTokenSource; currentComputeFunction = func; hasComputeFunctionChanged = false; var task = Task.Run(func); task.ContinueWith(t => { if (!cancellationTokenSource.IsCancelled(processOutdatedResults)) { resultProcessor(t.Result); } }, CancellationToken.None, TaskContinuationOptions.NotOnFaulted, resultTaskScheduler); task.ContinueWith(_ => { lock (stateLock) { cancellationTokenSource.Dispose(); if (currentCancellationTokenSource == cancellationTokenSource) { currentCancellationTokenSource = null; } if (hasComputeFunctionChanged && !wasHardCancelled) { EnqueueComputation(currentComputeFunction); } else { currentComputeFunction = null; hasComputeFunctionChanged = false; } } }); } public sealed class Single { private readonly AsyncValueComputer baseComputer; private readonly Func resultComputer; internal Single(AsyncValueComputer baseComputer, Func resultComputer) { this.baseComputer = baseComputer; this.resultComputer = resultComputer; } public void Recompute() { baseComputer.Compute(resultComputer); } } public static Builder WithResultProcessor(Action resultProcessor, TaskScheduler? scheduler = null) { return new Builder(resultProcessor, scheduler ?? TaskScheduler.FromCurrentSynchronizationContext()); } public sealed class Builder { private readonly Action resultProcessor; private readonly TaskScheduler resultTaskScheduler; private bool processOutdatedResults; internal Builder(Action resultProcessor, TaskScheduler resultTaskScheduler) { this.resultProcessor = resultProcessor; this.resultTaskScheduler = resultTaskScheduler; } public Builder WithOutdatedResults() { this.processOutdatedResults = true; return this; } public AsyncValueComputer Build() { return new AsyncValueComputer(resultProcessor, resultTaskScheduler, processOutdatedResults); } public Single BuildWithComputer(Func resultComputer) { return new Single(Build(), resultComputer); } } }