< Summary

Information
Class: MyNet.Utilities.Threading.SingleTaskDeferrer
Assembly: MyNet.Utilities
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Utilities/Threading/SingleTaskDeferrer.cs
Tag: 323_28699572109
Line coverage
69%
Covered lines: 39
Uncovered lines: 17
Coverable lines: 56
Total lines: 162
Line coverage: 69.6%
Branch coverage
85%
Covered branches: 17
Total branches: 20
Branch coverage: 85%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)75%4492.3%
.ctor(...)100%210%
OnRunningChanged(...)83.33%66100%
get_IsDeferred()100%210%
get_IsSuspended()100%210%
get_IsRunning()100%210%
Defer()100%11100%
Suspend()100%11100%
Request()100%11100%
Cancel()100%210%
FlushDeferredRequest()100%44100%
DispatchExecution()100%22100%
Dispose()75%4483.33%

File(s)

https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Utilities/Threading/SingleTaskDeferrer.cs

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="SingleTaskDeferrer.cs" company="Stéphane ANDRE">
 3// Copyright (c) Stéphane ANDRE. All rights reserved.
 4// </copyright>
 5// -----------------------------------------------------------------------
 6
 7using System;
 8using System.Threading;
 9using System.Threading.Tasks;
 10using Microsoft.Extensions.Logging;
 11using MyNet.Utilities.Deferring;
 12using MyNet.Utilities.Suspending;
 13
 14namespace MyNet.Utilities.Threading;
 15
 16/// <summary>
 17/// Coordinates deferred, optionally throttled refresh requests with at-most-one asynchronous execution.
 18/// While a task runs, a new request cancels it and schedules a rerun when the current execution ends.
 19/// </summary>
 20/// <remarks>
 21/// Compose <see cref="Deferring.DeferredAction"/> (batch updates), <see cref="Suspender"/> (temporary mute),
 22/// and <see cref="SingleTaskRunner"/> (single in-flight task). Use <see cref="Request"/> from property setters
 23/// and <see cref="Defer"/> when applying several changes in one scope.
 24/// </remarks>
 25public sealed class SingleTaskDeferrer : IDisposable, IDeferrable, ISuspendable
 26{
 27    private readonly DeferredAction _deferredAction;
 1528    private readonly Suspender _suspender = new();
 29    private readonly SingleTaskRunner _runner;
 30    private readonly Debouncer? _debouncer;
 31    private readonly Action<bool>? _onRunningChanged;
 32    private bool _mustRerun;
 33    private bool _disposed;
 34
 35    /// <summary>
 36    /// Initializes a new instance of the <see cref="SingleTaskDeferrer"/> class using a synchronous action (wrapped as 
 37    /// </summary>
 38    public SingleTaskDeferrer(
 39        Action<CancellationToken> action,
 40        Action<bool>? onRunningChanged = null,
 41        Action? onCancelled = null,
 42        int throttleMilliseconds = 0,
 43        ILogger? logger = null)
 044        : this(
 045            cancellationToken =>
 046            {
 047                action(cancellationToken);
 048                return Task.CompletedTask;
 049            },
 050            onRunningChanged,
 051            onCancelled,
 052            throttleMilliseconds,
 053            logger)
 54    {
 055    }
 56
 57    /// <summary>
 58    /// Initializes a new instance of the <see cref="SingleTaskDeferrer"/> class using an asynchronous action.
 59    /// </summary>
 60    public SingleTaskDeferrer(
 61        Func<CancellationToken, Task> action,
 62        Action<bool>? onRunningChanged = null,
 63        Action? onCancelled = null,
 64        int throttleMilliseconds = 0,
 65        ILogger? logger = null)
 66    {
 1567        ArgumentNullException.ThrowIfNull(action);
 68
 1569        if (throttleMilliseconds < 0)
 070            ArgumentOutOfRangeException.ThrowIfNegative(throttleMilliseconds);
 71
 1572        _deferredAction = new(FlushDeferredRequest);
 1573        _debouncer = throttleMilliseconds > 0 ? new Debouncer(throttleMilliseconds) : null;
 1574        _onRunningChanged = onRunningChanged;
 75
 1576        _runner = new(
 1577            action,
 1578            OnRunningChanged,
 1579            onCancelled,
 1580            logger);
 1581    }
 82
 83    private void OnRunningChanged(bool isRunning)
 84    {
 3085        _onRunningChanged?.Invoke(isRunning);
 86
 3087        if (isRunning || !_mustRerun)
 2788            return;
 89
 390        _mustRerun = false;
 391        _ = _runner.RunAsync();
 392    }
 93
 94    /// <summary>
 95    /// Gets a value indicating whether execution is currently deferred.
 96    /// </summary>
 097    public bool IsDeferred => _deferredAction.IsDeferred;
 98
 99    /// <summary>
 100    /// Gets a value indicating whether refresh requests are currently suspended.
 101    /// </summary>
 0102    public bool IsSuspended => _suspender.IsSuspended;
 103
 104    /// <summary>
 105    /// Gets a value indicating whether the underlying task is running.
 106    /// </summary>
 0107    public bool IsRunning => _runner.IsRunning;
 108
 109    /// <inheritdoc />
 3110    public IDisposable Defer() => _deferredAction.Defer();
 111
 112    /// <inheritdoc />
 3113    public IDisposable Suspend() => _suspender.Suspend();
 114
 115    /// <summary>
 116    /// Requests execution of the bound work, honoring deferral, suspension, and throttle settings.
 117    /// </summary>
 27118    public void Request() => _deferredAction.Request();
 119
 120    /// <summary>
 121    /// Cancels the current execution if any.
 122    /// </summary>
 0123    public void Cancel() => _runner.Cancel();
 124
 125    private void FlushDeferredRequest()
 126    {
 24127        if (_suspender.IsSuspended)
 3128            return;
 129
 21130        if (_debouncer is null)
 131        {
 12132            DispatchExecution();
 12133            return;
 134        }
 135
 9136        _debouncer.Schedule(DispatchExecution);
 9137    }
 138
 139    private void DispatchExecution()
 140    {
 15141        if (_runner.IsRunning)
 142        {
 3143            _mustRerun = true;
 3144            _runner.Cancel();
 3145            return;
 146        }
 147
 12148        _ = _runner.RunAsync();
 12149    }
 150
 151    /// <inheritdoc />
 152    public void Dispose()
 153    {
 15154        if (_disposed)
 0155            return;
 156
 15157        _disposed = true;
 15158        _debouncer?.Dispose();
 15159        _runner.Dispose();
 15160    }
 161}
 162