< Summary

Information
Class: MyNet.Utilities.Threading.SingleTaskRunner
Assembly: MyNet.Utilities
File(s): https://raw.githubusercontent.com/sandre58/MyNet/85372080fe102cd9ee155ceab49ae000e7f66103/src/MyNet.Utilities/Threading/SingleTaskRunner.cs
Tag: 323_28699572109
Line coverage
87%
Covered lines: 57
Uncovered lines: 8
Coverable lines: 65
Total lines: 202
Line coverage: 87.6%
Branch coverage
70%
Covered branches: 21
Total branches: 30
Branch coverage: 70%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)50%22100%
get_IsRunning()100%11100%
get_CurrentTask()100%11100%
Run()100%44100%
RunAsync()100%22100%
Cancel()50%4483.33%
ExecuteAsync()75%44100%
NotifyRunningChanged(...)50%6450%
OnCancelledSafe()50%6450%
Dispose()83.33%6690%

File(s)

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

#LineLine coverage
 1// -----------------------------------------------------------------------
 2// <copyright file="SingleTaskRunner.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.Logging;
 12
 13namespace MyNet.Utilities.Threading;
 14
 15/// <summary>
 16/// Provides a mechanism to run a single instance of an asynchronous task at a time, with support for cancellation and n
 17/// </summary>
 18/// <param name="action">The asynchronous action to execute.</param>
 19/// <param name="onRunningChanged">An optional callback invoked when the running state changes.</param>
 20/// <param name="onCancelled">An optional callback invoked when the task is canceled.</param>
 21/// <param name="logger">An optional logger for logging errors.</param>
 22public sealed class SingleTaskRunner(
 23    Func<CancellationToken, Task> action,
 24    Action<bool>? onRunningChanged = null,
 25    Action? onCancelled = null,
 26    ILogger? logger = null)
 27    : IDisposable
 28{
 3029    private readonly Lock _lock = new();
 3030    private readonly Func<CancellationToken, Task> _action = action ?? throw new ArgumentNullException(nameof(action));
 31    private CancellationTokenSource? _cts;
 32    private Task? _currentTask;
 33    private bool _isRunning;
 34    private bool _disposed;
 35
 36    /// <summary>
 37    /// Gets a value indicating whether a task is currently running.
 38    /// </summary>
 39    public bool IsRunning
 40    {
 41        get
 2142        {
 43            lock (_lock)
 44            {
 2145                return _isRunning;
 46            }
 2147        }
 48    }
 49
 50    /// <summary>
 51    /// Gets the currently running task if any.
 52    /// </summary>
 53    public Task? CurrentTask
 54    {
 55        get
 2756        {
 57            lock (_lock)
 58            {
 2759                return _currentTask;
 60            }
 2761        }
 62    }
 63
 64    /// <summary>
 65    /// Starts execution if no task is already running.
 66    /// </summary>
 67    /// <returns>
 68    /// True if the task was started; otherwise false.
 69    /// </returns>
 70    public bool Run()
 3671    {
 72        lock (_lock)
 73        {
 3674            ObjectDisposedException.ThrowIf(_disposed, this);
 75
 3376            if (_isRunning)
 677                return false;
 78
 2779            _cts?.Dispose();
 80
 2781            _cts = new();
 2782            var token = _cts.Token;
 83
 2784            _isRunning = true;
 85
 2786            _currentTask = ExecuteAsync(token);
 2787        }
 88
 2789        NotifyRunningChanged(true);
 90
 2791        return true;
 692    }
 93
 94    /// <summary>
 95    /// Starts execution if no task is already running and returns the running task.
 96    /// </summary>
 97    /// <returns>
 98    /// The running task, or null if another execution is already in progress.
 99    /// </returns>
 21100    public Task? RunAsync() => Run() ? CurrentTask : null;
 101
 102    /// <summary>
 103    /// Cancels the current execution.
 104    /// </summary>
 105    public void Cancel()
 6106    {
 107        lock (_lock)
 108        {
 6109            if (_disposed)
 0110                return;
 111
 6112            _cts?.Cancel();
 6113        }
 6114    }
 115
 116    /// <summary>
 117    /// Executes the provided asynchronous action, handling cancellation and exceptions, and ensuring that the running s
 118    /// </summary>
 119    /// <param name="cancellationToken">The cancellation token to observe.</param>
 120    private async Task ExecuteAsync(CancellationToken cancellationToken)
 121    {
 122        try
 123        {
 27124            await _action(cancellationToken).ConfigureAwait(false);
 18125        }
 6126        catch (OperationCanceledException)
 127        {
 6128            if (cancellationToken.IsCancellationRequested)
 6129                OnCancelledSafe();
 6130        }
 3131        catch (Exception ex)
 132        {
 3133            logger?.LogException(ex);
 3134        }
 135        finally
 27136        {
 137            lock (_lock)
 138            {
 27139                _isRunning = false;
 27140                _currentTask = null;
 27141            }
 142
 27143            NotifyRunningChanged(false);
 144        }
 27145    }
 146
 147    /// <summary>
 148    /// Notifies subscribers that the running state has changed, invoking the onRunningChanged callback if provided, and
 149    /// </summary>
 150    /// <param name="isRunning">A value indicating whether a task is currently running.</param>
 151    private void NotifyRunningChanged(bool isRunning)
 152    {
 153        try
 154        {
 54155            onRunningChanged?.Invoke(isRunning);
 54156        }
 0157        catch (Exception ex)
 158        {
 0159            logger?.LogException(ex);
 0160        }
 54161    }
 162
 163    /// <summary>
 164    /// Notifies subscribers that the task has been canceled, invoking the onCancelled callback if provided, and logging
 165    /// </summary>
 166    private void OnCancelledSafe()
 167    {
 168        try
 169        {
 6170            onCancelled?.Invoke();
 6171        }
 0172        catch (Exception ex)
 173        {
 0174            logger?.LogException(ex);
 0175        }
 6176    }
 177
 178    /// <summary>
 179    /// Disposes the SingleTaskRunner, ensuring that any running task is canceled and that resources are released approp
 180    /// </summary>
 181    public void Dispose()
 30182    {
 183        lock (_lock)
 184        {
 30185            if (_disposed)
 0186                return;
 187
 30188            _disposed = true;
 189
 190            try
 191            {
 30192                _cts?.Cancel();
 24193            }
 194            finally
 195            {
 30196                _cts?.Dispose();
 30197                _cts = null;
 30198            }
 199        }
 30200    }
 201}
 202