Библиотека классов C # для управления повторяющимися фоновыми операциями

Я написал библиотеку классов для создания фоновых операций на основе таймера в проектах .NET. Идея состоит в том, чтобы иметь возможность создавать и управлять (запускать / останавливать / возобновлять / отменять) повторяющиеся фоновые операции, а также иметь возможность опрашивать и / или реагировать на изменения в состоянии фоновой операции.

Библиотека состоит в основном из двух вещей:

RecurringOperation модель / класс

  • Инкапсулирует и представляет одно действие / метод, который выполняется через определенные промежутки времени.
  • Представляет состояние повторяющейся операции, предоставляя свойства и события для привязки.
  • Поддерживает шаблон XAML и MVVM для привязки элементов управления пользовательского интерфейса к состоянию повторяющейся операции.

RecurringOperationManager одноэлементный класс

  • Отвечает за управление всеми объектами RecurringOperation внутри приложения (запуск / остановка / отмена).
  • Отвечает за выполнение повторяющихся операций потокобезопасным способом и следит за тем, чтобы одна повторяющаяся операция не «ставилась в очередь», если выполнение повторяющейся операции занимает больше времени, чем указанный интервал для этой операции.

Я хотел бы узнать ваше мнение о

  • Общее качество и удобочитаемость кода (любые явные баги или ошибки)
  • Архитектура и реализация RecurringOperation а также RecurringOperationManager классы

Чтобы получить общее представление об использовании библиотеки, ознакомьтесь с файлом readme на странице github по адресу
https://github.com/TDMR87/Recurop

Чтобы увидеть используемую библиотеку, взгляните на эту страницу github. Проект создает программу секундомера в WPF с использованием этой библиотеки.
https://github.com/TDMR87/RecuropDemo


Реализация RecurringOperation класс выглядит так:

public class RecurringOperation : INotifyPropertyChanged
    {
        public RecurringOperation(string name)
        {
            if (string.IsNullOrWhiteSpace(name))
                throw new InvalidOperationException(Constants.UnnamedOperationException);

            _name = name;
            IsExecuting = false;
            IsRecurring = false;
            IsNotRecurring = true;
            IsPaused = false;
            IsIdle = true;
            IsCancelled = false;
            IsNotCancelled = true;
            CallbackLock = new object();
            Status = RecurringOperationStatus.Idle;
            CanBeStarted = true;
        }
        private readonly string _name;

        /// <summary>
        /// Indicates whether the recurring operation can be started.
        /// </summary>
        public bool CanBeStarted
        {
            get => canBeStarted;
            internal set
            {
                canBeStarted = value;
                OnPropertyChanged();
            }
        }
        private bool canBeStarted;

        /// <summary>
        /// Indicates whether the recurring background operation is 
        /// currently executing it's specified action.
        /// </summary>
        public bool IsExecuting
        {
            get => isExecuting;
            internal set
            {
                isExecuting = value;
                OnPropertyChanged();
            }
        }
        private bool isExecuting;

        /// <summary>
        /// Indicates whether the recurring background operation is 
        /// currently in recurring state (and not cancelled, for example).
        /// </summary>
        public bool IsRecurring
        {
            get => isRecurring;
            internal set
            {
                isRecurring = value;
                OnPropertyChanged();
            }
        }
        private bool isRecurring;

        /// <summary>
        /// Indicates whether the recurring background operation is 
        /// currently in recurring state.
        /// </summary>
        public bool IsNotRecurring
        {
            get => isNotRecurring;
            internal set
            {
                isNotRecurring = value;
                OnPropertyChanged();
            }
        }
        private bool isNotRecurring;

        /// <summary>
        /// Indicates whether the recurring background operation is 
        /// currently in cancelled state.
        /// </summary>
        public bool IsCancelled
        {
            get => isCancelled;
            internal set
            {
                isCancelled = value;
                OnPropertyChanged();
            }
        }
        private bool isCancelled;

        /// <summary>
        /// Indicates whether the recurring background operation is 
        /// currently not in cancelled state.
        /// </summary>
        public bool IsNotCancelled
        {
            get => isNotCancelled;
            internal set
            {
                isNotCancelled = value;
                OnPropertyChanged();
            }
        }
        private bool isNotCancelled;

        /// <summary>
        /// Indicates whether the recurring operation is currently paused.
        /// </summary>
        public bool IsPaused
        {
            get => isPaused;
            internal set
            {
                isPaused = value;
                OnPropertyChanged();
            }
        }
        private bool isPaused;

        /// <summary>
        /// Indicates whether the recurring operation is idle. An idle state means that 
        /// the operation is not yet started or it has been aborted.
        /// </summary>
        public bool IsIdle
        {
            get => isIdle;
            set
            {
                isIdle = value;
                OnPropertyChanged();
            }
        }
        private bool isIdle;

        /// <summary>
        /// The start time of the latest background operation execution.
        /// </summary>
        public DateTime LastRunStart
        {
            get => lastRunStart;
            internal set
            {
                lastRunStart = value;
                OnPropertyChanged();
            }
        }
        private DateTime lastRunStart;

        /// <summary>
        /// The start time of the latest background operation execution.
        /// </summary>
        public DateTime LastRunFinish
        {
            get => lastRunFinish;
            internal set
            {
                lastRunFinish = value;
                OnPropertyChanged();
            }
        }
        private DateTime lastRunFinish;

        public RecurringOperationStatus Status
        {
            get => status;
            internal set
            {
                status = value;
                OnStatusChanged();
                OnPropertyChanged();
            }
        }
        private RecurringOperationStatus status;

        /// <summary>
        /// A lock object used to prevent overlapping threads from modifying
        /// the properties of the Background Operation object. Use with lock-statement.
        /// </summary>
        internal object CallbackLock { get; }

        /// <summary>
        /// When the Background Operation's action delegate throws an exception,
        /// the exception is accessible through this property. The next exception
        /// in the action delegate will override any previous value in this property.
        /// </summary>
        public Exception Exception
        {
            get => exception;
            internal set
            {
                exception = value;
                OnOperationFaulted();
            }
        }
        private Exception exception;

        /// <summary>
        /// The event handler is triggered whenever the status of
        /// the background operation changes.
        /// </summary>
        public event Action StatusChanged;

        /// <summary>
        /// The event handler is triggered when the
        /// Background Operation's action delegate throws an exception.
        /// </summary>
        public event Action<Exception> OperationFaulted;

        /// <summary>
        /// The event handler is triggered when properties of the Background Operation
        /// change value.
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        protected void OnPropertyChanged([CallerMemberName] string name = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
        }

        protected void OnStatusChanged()
        {
            StatusChanged?.Invoke();
        }

        protected void OnOperationFaulted()
        {
            OperationFaulted?.Invoke(Exception);
        }

        /// <summary>
        /// Returns the identifying name of this background operation.
        /// </summary>
        /// <returns></returns>
        public string GetName()
        {
            if (string.IsNullOrWhiteSpace(_name))
            {
                throw new InvalidOperationException(Constants.UninitializedOperationException);
            }

            return _name;
        }

        /// <summary>
        /// Returns the identifying name of this background operation.
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            if (string.IsNullOrWhiteSpace(_name))
            {
                throw new InvalidOperationException(Constants.UninitializedOperationException);
            }

            return _name;
        }
    }

А класс RecurringOperationManager выглядит так:

public sealed class RecurringOperations
    {
        private static RecurringOperations instance = null;
        private static readonly object instanceLock = new object();
        private readonly List<Operation> recurringOperations;

        /// <summary>
        /// Private singleton constructor.
        /// </summary>
        private RecurringOperations()
        {
            recurringOperations = new List<Operation>();
        }

        /// <summary>
        /// Public singleton instance.
        /// </summary>
        public static RecurringOperations Manager
        {
            get
            {
                lock (instanceLock)
                {
                    if (instance == null) instance = new RecurringOperations();
                    return instance;
                }
            }
        }

        /// <summary>
        /// Resumes the execution of the specified recurring operation 
        /// if currently in Paused state. Resuming a cancelled operation
        /// throws an exception.
        /// </summary>
        /// <param name="operation"></param>
        /// <param name="interval"></param>
        /// <param name="action"></param>
        /// <param name="startImmediately"></param>
        public void ResumeRecurring(RecurringOperation operation, bool startImmediately = false)
        {
            // If an uninitialized background operation object was given as an argument
            if (operation == null || string.IsNullOrWhiteSpace(operation.GetName()))
                throw new InvalidOperationException(Constants.UninitializedOperationException);

            // If cancelled
            if (operation.Status == RecurringOperationStatus.Cancelled)
            {
                var exception = new InvalidOperationException(Constants.CancelledOperationException);

                operation.Exception = exception;

                throw exception;
            }

            // Search the Manager's collection of operations for an operation
            var recurringOperation =
                recurringOperations.Find(op => op.Name.Equals(operation.GetName()));

            // If the operation was found and is not recurring
            if (recurringOperation != null)
            {
                // Set background operation status
                operation.Status = RecurringOperationStatus.Idle;
                operation.IsIdle = true;
                operation.IsRecurring = true;
                operation.IsNotRecurring = false;
                operation.IsPaused = false;
                operation.IsExecuting = false;

                // Re-start the operation
                recurringOperation.Timer.Change(
                    startImmediately ? TimeSpan.Zero : recurringOperation.Interval, recurringOperation.Interval);
            }
        }

        /// <summary>
        /// Creates and/or starts a recurring operation.
        /// If an operation has already been started,
        /// an exception will be thrown.
        /// </summary>
        /// <param name="backgroundOperation"></param>
        /// <param name="interval"></param>
        /// <param name="action"></param>
        /// <param name="startImmediately"></param>
        public void StartRecurring(RecurringOperation backgroundOperation, TimeSpan interval, Action action, bool startImmediately = false)
        {
            // If an uninitialized background operation object was given as an argument
            if (backgroundOperation == null || string.IsNullOrWhiteSpace(backgroundOperation.GetName()))
                throw new InvalidOperationException(Constants.UninitializedOperationException);

            // Search the Manager's collection of operations for an operation
            // that corresponds with the background operation
            var recurringOperation =
                recurringOperations.Find(op => op.Name.Equals(backgroundOperation.GetName()));

            // If operation has already been registered
            if (recurringOperation != null)
            {
                throw new InvalidOperationException($"{Constants.RecurringOperationException}. " +
                                                    $"Operation name '{recurringOperation.Name}'.");
            }

            // Create a new recurring operation object
            var operation = new Operation
            {
                Name = backgroundOperation.GetName(),
                Interval = interval
            };

            // This is a local function here..
            // ..Create a callback function for the timer
            void TimerCallback(object state)
            {
                // To indicate if a thread has entered 
                // a block of code.
                bool lockAcquired = false;

                try
                {
                    // Try to acquire a lock.
                    // Sets the value of the lockAcquired, even if the method throws an exception, 
                    // so the value of the variable is a reliable way to test whether the lock has to be released.
                    Monitor.TryEnter(backgroundOperation.CallbackLock, ref lockAcquired);

                    // If lock acquired
                    if (lockAcquired)
                    {
                        // Set background operation status
                        backgroundOperation.LastRunStart = DateTime.Now;
                        backgroundOperation.IsExecuting = true;
                        backgroundOperation.IsIdle = false;
                        backgroundOperation.Status = RecurringOperationStatus.Executing;

                        try
                        {
                            action();
                        }
                        catch (Exception ex)
                        {
                            // Set reference to the catched exception in the background operations exception property
                            backgroundOperation.Exception = ex;
                        }
                        finally
                        {
                            // Set last run finish time
                            backgroundOperation.LastRunFinish = DateTime.Now;

                            // Set background operation state
                            backgroundOperation.IsExecuting = false;
                            backgroundOperation.IsIdle = true;

                            // If not cancelled
                            if (backgroundOperation.Status != RecurringOperationStatus.Cancelled)
                            {
                                // Set status to idle
                                backgroundOperation.Status = RecurringOperationStatus.Idle;
                            }
                        }
                    }
                }
                finally
                {
                    // If a thread acquired the lock
                    if (lockAcquired)
                    {
                        // Release the lock
                        Monitor.Exit(backgroundOperation.CallbackLock);
                    }
                }
            } // End local funtion

            // Create a timer that calls the action in specified intervals
            // and save the timer in the recurring operations Timer property
            operation.Timer = new Timer(
                new TimerCallback(TimerCallback), null, startImmediately ? TimeSpan.Zero : interval, interval);

            // Add the recurring operation to the Manager's collection
            recurringOperations.Add(operation);

            // Set status of the client-side background operation
            backgroundOperation.Status = RecurringOperationStatus.Idle;
            backgroundOperation.IsRecurring = true;
            backgroundOperation.IsNotRecurring = false;
            backgroundOperation.IsPaused = false;
            backgroundOperation.IsIdle = false;
            backgroundOperation.CanBeStarted = false;
        }

        /// <summary>
        /// Pauses the recurring execution of the operation.
        /// Execution can be continued with a call to ResumeRecurring().
        /// </summary>
        /// <param name="backgroundOperation"></param>
        public void PauseRecurring(RecurringOperation backgroundOperation)
        {
            // Search the Manager's collection of operations for an operation
            // that corresponds with the background operation
            var privateOperation =
                recurringOperations.Find(
                    operation => operation.Name.Equals(backgroundOperation.GetName()));

            // If found
            if (privateOperation != null)
            {
                // Pause the timer
                privateOperation.Timer.Change(Timeout.Infinite, Timeout.Infinite);

                // Set the status of the client-side background operation
                backgroundOperation.Status = RecurringOperationStatus.Idle;
                backgroundOperation.IsRecurring = false;
                backgroundOperation.IsNotRecurring = true;
                backgroundOperation.IsPaused = true;
                backgroundOperation.IsIdle = true;
                backgroundOperation.IsExecuting = false;
            }
        }

        /// <summary>
        /// Cancels the specified recurring operation. 
        /// Cancelled operations cannot be resumed, they must be restarted
        /// with a call to StartRecurring.
        /// </summary>
        /// <param name="backgroundOperation"></param>
        public void Cancel(RecurringOperation backgroundOperation)
        {
            // Search the Manager's collection of operations for an operation
            // that corresponds with the background operation
            var privateOperation =
                recurringOperations.Find(
                    operation => operation.Name.Equals(backgroundOperation.GetName()));

            // If found
            if (privateOperation != null)
            {
                // Dispose the timer and the operation object
                privateOperation.Timer.Dispose();
                recurringOperations.Remove(privateOperation);

                // Set the status of the client-side background operation
                backgroundOperation.Status = RecurringOperationStatus.Cancelled;
                backgroundOperation.IsRecurring = false;
                backgroundOperation.IsNotRecurring = true;
                backgroundOperation.IsPaused = false;
                backgroundOperation.IsExecuting = false;
                backgroundOperation.IsIdle = true;
                backgroundOperation.IsCancelled = true;
                backgroundOperation.IsNotCancelled = false;
                backgroundOperation.CanBeStarted = true;
            }
        }

        /// <summary>
        /// Private operation data structure.
        /// </summary>
        private class Operation
        {
            internal string Name { get; set; }
            internal Timer Timer { get; set; }
            internal TimeSpan Interval { get; set; }
        }
    }

0

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *