Monday, December 30, 2013

Рефакторинг многопоточного приложения

Идея этой статьи возникла спонтанно, после правки кода по работе с потоками. Поскольку такой катастрофичной ситуации не видел достаточно давно, решил привести пример рефакторинга подобного кода, чтобы не повторять данных ошибок. С основными принципами рефакторинга можно ознакомиться по книге "Рефакторинг.Улучшение существующего кода"
Данная статья написана с уклоном на тему борьбы с таким понятием, как "code smell". Первый вариант этого кода был написан на .NET Framework 2.0, а затем улучшен с добавлением возможностей .NET Framework 4.0. Давайте рассмотрим это чудотворение для управления выполнением функций в отдельных потоках. 
public sealed class ActionWorker<T> : IDisposable where T : class
{
    private readonly object _locker;
    private readonly object _releaseLocker;
    private Thread _worker;
       
    private IEnumerable<CustomActionBase<T>> _interruptableActions;
    private IEnumerable<CustomActionBase<T>> _releaseActions;
    private IEnumerable<CustomActionBase<T>> _initializeActions;

    private bool _canceling;
    private bool _pausing;
    private bool _completed;
    private int _releaseEnters;
    private Task _initializeActionsTask;

    /// <summary>
    /// Действия завершены
    /// </summary>
    public event EventHandler ExecutionCompleted;

    /// <summary>
    /// Запускается новое действие
    /// </summary>
    public event EventHandler NewActionStarting;

    public CustomActionBase<T> CurrentAction { get; private set; }

    public Exception Exception { get; private set; }

    public ActionWorker(IEnumerable<CustomActionBase<T>> actions)
    {
        _locker = new object();
        _releaseLocker = new object();
           
        _initializeActionsTask = InitializeTasks(actions);
        _initializeActionsTask.Start();
        _worker = new Thread(RunMethod);
    }

    private Task InitializeTasks(IEnumerable<CustomActionBase<T>> actions)
    {
        var task = new Task(
            () =>
            {
                var actionsArray = actions.ToArray();
                _releaseActions = GetReleaseActions(actionsArray);
                _interruptableActions = GetNormalActions(actionsArray);
                _initializeActions = GetInitializeActions(actionsArray);
            });

        return task;
    }

    /// <summary>
    /// Запустить выполнение операций в отдельном потоке
    /// </summary>
    public void Start()
    {
        if (_completed)
            return;

        lock (_locker)
        {
            _canceling = false;
            _pausing = false;
        }

        Task.WaitAll(_initializeActionsTask);
        _worker.Start();
    }

    /// <summary>
    /// Продолжить выполнение после остановки
    /// </summary>
    public void Continue()
    {
        if (!_completed)
            lock (_locker)
                _pausing = false;
    }

    /// <summary>
    /// Приостановка выполнения
    /// </summary>
    public void Pause()
    {
        if (!_completed)
            lock (_locker)
                _pausing = true;
    }

    /// <summary>
    /// Завершить выполнение операций
    /// </summary>
    public void Stop()
    {
        if (_completed)
            return;

        lock (_locker)
            _canceling = true;

        while (!_worker.Join(100))
            Application.DoEvents();
    }

    /// <summary>
    /// Возможность запуска действий завершения
    /// </summary>
    private bool CanExecuteReleaseActions
    {
        get
        {
            lock (_releaseLocker)
                return _releaseEnters == 0;
        }
    }

    private void ReleaseEnter()
    {
        lock(_releaseLocker)
            _releaseEnters++;
    }

    private void ReleaseExit()
    {
        lock (_releaseLocker)
            _releaseEnters--;
    }

    /// <summary>
    /// Получить последовательность действий
    /// </summary>
    /// <param name="actions"></param>
    /// <returns></returns>
    private static IEnumerable<CustomActionBase<T>> GetNormalActions(IEnumerable<CustomActionBase<T>> actions)
    {
        return GetActions(actions, CustomActionTypes.Normal);
    }

    /// <summary>
    /// Получить последовательность действий завершения
    /// </summary>
    /// <param name="actions"></param>
    /// <returns></returns>
    private static IEnumerable<CustomActionBase<T>> GetReleaseActions(IEnumerable<CustomActionBase<T>> actions)
    {
        return GetActions(actions, CustomActionTypes.Release);
    }

    /// <summary>
    /// Получить последовательность действий инициализации
    /// </summary>
    /// <param name="actions"></param>
    /// <returns></returns>
    private static IEnumerable<CustomActionBase<T>> GetInitializeActions(IEnumerable<CustomActionBase<T>> actions)
    {
        return GetActions(actions, CustomActionTypes.Initialize);
    }

    private static IEnumerable<CustomActionBase<T>> GetActions(IEnumerable<CustomActionBase<T>> actions, CustomActionTypes actionType)
    {
        return actions.Where(action => action.ActionType == actionType);
    }

    private void RunMethod()
    {
        lock (_releaseLocker)
            _releaseEnters = 0;

        ExecuteInitializeActions();

        ExecuteInterruptableActions();

        lock(_locker)
            _completed = true;

        if (!_canceling)
            RaiseEvent(ExecutionCompleted);
    }

    private void RaiseEvent(EventHandler handler)
    {
        if (handler == null)
            return;

        var mainForm = ApplicationSettings.MainForm;

        if(mainForm != null)
            ControlExtensions.Invoke(mainForm, handler, this, EventArgs.Empty);
    }       

    private void ExecuteReleaseActions()
    {
        ExecuteNonInterruptableActions(_releaseActions);           
    }

    private void ExecuteReleaseActionsSilently()
    {
        ExecuteNonInterruptableActionsSilently(_releaseActions);
    }

    private void ExecuteInitializeActions()
    {
        ExecuteNonInterruptableActions(_initializeActions);
    }

    private void ExecuteInterruptableActions()
    {
        ReleaseEnter();

        foreach (var action in _interruptableActions)
        {
            while (_pausing)
            {
                if (_canceling)
                    break;

                Thread.Sleep(100);
            }

            if (_canceling)
                break;

            var asyncAction = (Action<CustomActionBase<T>>)ExecuteAction;

            ReleaseEnter();

            var asyncResult = asyncAction.BeginInvoke(action, OnActionCompleted, null);

            while (!asyncResult.IsCompleted && !_canceling)
                Thread.Sleep(100);

            if (asyncResult.IsCompleted)
                try
                {
                    asyncAction.EndInvoke(asyncResult);
                }
                catch(Exception ex)
                {
                    ExecuteReleaseActionsSilently();
                    Exception = ex;
                    RaiseEvent(ExecutionCompleted);
                }
                   
            else break;
        }

        ConditionallyExecuteReleaseActions(false);
    }

    public void OnActionCompleted(IAsyncResult result)
    {
        ConditionallyExecuteReleaseActions(true);
    }

    /// <summary>
    /// Запуск действий завершения после последней выполняющейся операции
    /// </summary>
    /// <param name="silently">Тихий режим (без отображения на форме)</param>
    private void ConditionallyExecuteReleaseActions(bool silently)
    {
        lock (_releaseLocker)
        {
            ReleaseExit();

            if (CanExecuteReleaseActions)
                if (silently)
                    ExecuteReleaseActionsSilently();
                else
                    ExecuteReleaseActions();
        }
    }

    private void ExecuteNonInterruptableActions(IEnumerable<CustomActionBase<T>> actions)
    {
        actions.ToList().ForEach(ExecuteAction);
    }

    private void ExecuteNonInterruptableActionsSilently(IEnumerable<CustomActionBase<T>> actions)
    {
        actions.ToList().ForEach(ExecuteActionSilently);
    }

    private void ExecuteAction(CustomActionBase<T> action)
    {
        lock (_locker)
            CurrentAction = action;

        if (!_canceling)
            RaiseEvent(NewActionStarting);

        action.Execute();
    }

    /// <summary>
    /// Запускается в случае отмены операции пользователем
    /// В данном случае событие о начале выполнения очередного завершающего действия не
    /// генерируется, т.к. форма, отображающая данную информацию уже может быть
    /// разрушена
    /// </summary>
    /// <param name="action"></param>
    private void ExecuteActionSilently(CustomActionBase<T> action)
    {
        action.Execute();
    }

    public void Dispose()
    {
        using (_initializeActionsTask)
        {
            _initializeActionsTask = null;
        }

        _interruptableActions = null;
        _releaseActions = null;
        _initializeActions = null;
        _worker = null;
    }
}
Задача данного класса - выполнять некоторую последовательность функций в отдельном потоке. Для данного класса асинхронная модель поведения реализована неверно. Этот класс можно полностью переделать на асинхронный вариант. Переделаем пример согласно C# 4.0 с использованием новых возможностей. Для этого нужно посмотреть, что неправильно в этой реализации. Первая ошибка этого класса находится в коде.
public ActionWorker(IEnumerable<CustomActionBase<T>> actions)
{
    _locker = new object();
    _releaseLocker = new object();
           
    _initializeActionsTask = InitializeTasks(actions);
    _initializeActionsTask.Start();
    _worker = new Thread(RunMethod);
}

private Task InitializeTasks(IEnumerable<CustomActionBase<T>> actions)
{
    var task = new Task(
        () =>
        {
            var actionsArray = actions.ToArray();
            _releaseActions = GetReleaseActions(actionsArray);
            _interruptableActions = GetNormalActions(actionsArray);
            _initializeActions = GetInitializeActions(actionsArray);
        });

    return task;
}

/// <summary>
/// Запустить выполнение операций в отдельном потоке
/// </summary>
public void Start()
{
    if (_completed)
        return;

    lock (_locker)
    {
        _canceling = false;
        _pausing = false;
    }

    Task.WaitAll(_initializeActionsTask);
    _worker.Start();
}
private void RunMethod()
{
    lock (_releaseLocker)
        _releaseEnters = 0;

    ExecuteInitializeActions();

    ExecuteInterruptableActions();

    lock(_locker)
        _completed = true;

    if (!_canceling)
        RaiseEvent(ExecutionCompleted);
}
Все неверные места в этом коде выделены красным. Я решил рассматривать реализацию блоками и сразу показать, как этот код можно исправить. Первая ошибка этого кода - это использование класса Task, который разбивает события, которые должны быть выполнены по типам. Разбиение происходит следующим методом, который будет бегать по текущей коллекции и разбивать ее на под коллекции для выполнения.
private static IEnumerable<CustomActionBase<T>> GetActions(IEnumerable<CustomActionBase<T>> actions, CustomActionTypes actionType)
    {
        return actions.Where(action => action.ActionType == actionType);
    }
В данном коде будет использован проход по коллекции три раза. Проблема того, что проход по коллекции будет осуществлен три раза, является менее серьёзной, чем создание для этой цели отдельного таска. В .NET Framework 4 для тасков используется выделенный системой процессор и ресурсы системы для создания и инициализации этого таска. Поэтому Вы можете повысить производительность выполнения Вашего кода, если будете грамотно использовать такой подход. Использование тасков в C#  Для такого случая это не даст ни выигрыша, не производительности. Если бы нам нужно было бы заполнить создание какой-то коллекции отложено, то для этого существует класс Lazy<T>, который отлично справляется со своей задачей. Основной проблемой данного подхода является неумение правильно  использовать таски. Автор этого кода, по-видимому, является ярым поклонником класса Thread и для решения данной проблемы мыслит потоками, а не задачами (тасками), что в корне неверно. Дальше проблема проявляется через использование переменных
private bool _canceling;
private bool _pausing;
private bool _completed;
private int _releaseEnters;
и постоянное их изменение с помощью конструкции lock. Давайте рассмотрим, где эти переменные используются, и нужно ли вообще такое количество переменных.
/// <summary>
/// Приостановка выполнения
/// </summary>
public void Pause()
{
    if (!_completed)
        lock (_locker)
            _pausing = true;
}

/// <summary>
/// Завершить выполнение операций
/// </summary>
public void Stop()
{
    if (_completed)
        return;

    lock (_locker)
        _canceling = true;

    while (!_worker.Join(100))
        Application.DoEvents();
}
private void ExecuteInterruptableActions()
{
    ReleaseEnter();

    foreach (var action in _interruptableActions)
    {
        while (_pausing)
        {
            if (_canceling)
                break;

            Thread.Sleep(100);
        }

        if (_canceling)
            break;

        var asyncAction = (Action<CustomActionBase<T>>)ExecuteAction;

        ReleaseEnter();

        var asyncResult = asyncAction.BeginInvoke(action, OnActionCompleted, null);

        while (!asyncResult.IsCompleted && !_canceling)
            Thread.Sleep(100);

        if (asyncResult.IsCompleted)
            try
            {
                asyncAction.EndInvoke(asyncResult);
            }
            catch(Exception ex)
            {
                ExecuteReleaseActionsSilently();
                Exception = ex;
                RaiseEvent(ExecutionCompleted);
            }
                   
        else break;
    }

    ConditionallyExecuteReleaseActions(false);
}
Если рассмотреть методы, используемые этими переменными, то часть из них устанавливает конкретным переменным некоторое значение, а вторая часть просто следит за этими переменными и изменяет поведение, в зависимости от состояния этих переменных. В таком подходе есть два негативных момента. Первая серьёзная ошибка, которую можно было допустить в коде, - это использование Thread.Sleep(100). При использовании в коде Thread.Sleep с вероятностью в 90% допущена ошибка и неверно спроектирована система в этом месте. Мне не приходилось встречать такой код, который не обходится без Thread.Sleep.
Второй негативный момент - это использование подхода Asynchronous Programming Model (APM), основанного на BeginInvoke, EndInvoke. Вместо использования тасков, где они не нужны, автор данного кода не удосужился использовать таски там, где они очень неплохо пригодились бы. Данный метод можно полностью переписать для выполнения приведенного цикла в асинхронном режиме с использованием паттерна «WaitAllOneByOne», который здесь был бы очень уместным, и для прекращения выполнения тасков использовать CancellationToken. Но поскольку данное исправление сильно повлияет на места с использованием данного кода, мы сделаем рефакторинг данного кода. Давайте рассмотрим, что нам для этого нужно:
  1. Максимально убрать использование переменных класса.
  2. Избавиться в коде от Thread.Sleep.
  3. Сократить количество конструкций lock для синхронизации.
  4. Перейти из APM модели на модель основанную на Task.
Для инкрементации переменных целого типа вместо конструкции lock, будем использовать конструкции класса Interlocked. Чтобы не использовать инструкцию Thread.Sleep и не устанавливать булевые переменные, воспользуемся классом  ManualResetEvent. Давайте посмотрим, как изменился класс.
public sealed class ActionWorker<T> : IDisposable where T : class
{
    private readonly object _locker;
    private readonly object _releaseLocker;
    private Thread _worker;

    private IEnumerable<CustomActionBase<T>> _interruptableActions;
    private IEnumerable<CustomActionBase<T>> _releaseActions;
    private IEnumerable<CustomActionBase<T>> _initializeActions;

    private readonly ManualResetEvent _shutdownEvent;
    private readonly ManualResetEvent _pauseEvent;

    private bool _completed;
    private int _releaseEnters;

    /// <summary>
    /// Действия завершены
    /// </summary>
    public event EventHandler ExecutionCompleted;

    /// <summary>
    /// Запускается новое действие
    /// </summary>
    public event EventHandler NewActionStarting;

    public CustomActionBase<T> CurrentAction { get; private set; }

    public Exception Exception { get; private set; }

    public ActionWorker(IEnumerable<CustomActionBase<T>> actions)
    {
        _locker = new object();
        _releaseLocker = new object();
        _shutdownEvent = new ManualResetEvent(false);
        _pauseEvent = new ManualResetEvent(true);
        InitializeAction(actions);
        _worker = new Thread(RunMethod);
    }

    private void InitializeAction(IEnumerable<CustomActionBase<T>> actions)
    {
        var actionsArray = actions.ToArray();
        _releaseActions = GetReleaseActions(actionsArray);
        _interruptableActions = GetNormalActions(actionsArray);
        _initializeActions = GetInitializeActions(actionsArray);
    }

    /// <summary>
    /// Запустить выполнение операций в отдельном потоке
    /// </summary>
    public void Start()
    {
        if (_completed)
            return;

        _worker.Start();
    }

    /// <summary>
    /// Продолжить выполнение после остановки
    /// </summary>
    public void Continue()
    {
        if (_completed)
            return;

        _pauseEvent.Set();
    }

    /// <summary>
    /// Приостановка выполнения
    /// </summary>
    public void Pause()
    {
        if (_completed)
            return;

        _pauseEvent.Reset();
    }

    /// <summary>
    /// Завершить выполнение операций
    /// </summary>
    public void Stop()
    {
        if (_completed)
            return;

        _shutdownEvent.Set();

        if(!_worker.Join(100))
            Application.DoEvents();
    }

    /// <summary>
    /// Возможность запуска действий завершения
    /// </summary>
    private bool CanExecuteReleaseActions
    {
        get
        {
            lock (_releaseLocker)
                return _releaseEnters == 0;
        }
    }

    private void ReleaseEnter()
    {
        Interlocked.Increment(ref _releaseEnters);
    }

    private void ReleaseExit()
    {
        Interlocked.Decrement(ref _releaseEnters);
    }

    /// <summary>
    /// Получить последовательность действий
    /// </summary>
    /// <param name="actions"></param>
    /// <returns></returns>
    private static IEnumerable<CustomActionBase<T>> GetNormalActions(IEnumerable<CustomActionBase<T>> actions)
    {
        return GetActions(actions, CustomActionTypes.Normal);
    }

    /// <summary>
    /// Получить последовательность действий завершения
    /// </summary>
    /// <param name="actions"></param>
    /// <returns></returns>
    private static IEnumerable<CustomActionBase<T>> GetReleaseActions(IEnumerable<CustomActionBase<T>> actions)
    {
        return GetActions(actions, CustomActionTypes.Release);
    }

    /// <summary>
    /// Получить последовательность действий инициализации
    /// </summary>
    /// <param name="actions"></param>
    /// <returns></returns>
    private static IEnumerable<CustomActionBase<T>> GetInitializeActions(IEnumerable<CustomActionBase<T>> actions)
    {
        return GetActions(actions, CustomActionTypes.Initialize);
    }

    private static IEnumerable<CustomActionBase<T>> GetActions(IEnumerable<CustomActionBase<T>> actions, CustomActionTypes actionType)
    {
        return actions.Where(action => action.ActionType == actionType);
    }

    private void RunMethod()
    {
        while (true)
        {
            _pauseEvent.WaitOne(Timeout.Infinite);

            if (_shutdownEvent.WaitOne(0))
                break;

            Interlocked.Exchange(ref _releaseEnters, 0);

            ExecuteInitializeActions();

            ExecuteInterruptableActions();

            lock (_locker)
                _completed = true;

            RaiseEvent(ExecutionCompleted);

            break;
        }
    }

    private void RaiseEvent(EventHandler handler)
    {
        if (handler == null)
            return;

        var mainForm = ApplicationSettings.MainForm;

        if (mainForm != null)
            ControlExtensions.Invoke(mainForm, handler, this, EventArgs.Empty);
    }

    private void ExecuteReleaseActions()
    {
        ExecuteNonInterruptableActions(_releaseActions);
    }

    private void ExecuteReleaseActionsSilently()
    {
        ExecuteNonInterruptableActionsSilently(_releaseActions);
    }

    private void ExecuteInitializeActions()
    {
        ExecuteNonInterruptableActions(_initializeActions);
    }

    private void ExecuteInterruptableActions()
    {
        ReleaseEnter();

        foreach (var action in _interruptableActions)
        {
            if (_shutdownEvent.WaitOne(0))
                break;

            ReleaseEnter();

            var task = Task.Factory.StartNew(actionBase =>
            {
                var localAction = (CustomActionBase<T>)actionBase;
                ExecuteAction(localAction);

            }, action);
            task.ContinueWith(result =>
            {
                ExecuteReleaseActionsSilently();
                Exception = result.Exception;
                RaiseEvent(ExecutionCompleted);
            },
                TaskContinuationOptions.OnlyOnFaulted);
            task.Wait();

            OnActionCompleted();
        }

        ConditionallyExecuteReleaseActions(false);
    }

    public void OnActionCompleted()
    {
        ConditionallyExecuteReleaseActions(true);
    }

    /// <summary>
    /// Запуск действий завершения после последней выполняющейся операции
    /// </summary>
    /// <param name="silently">Тихий режим (без отображения на форме)</param>
    private void ConditionallyExecuteReleaseActions(bool silently)
    {
        lock (_releaseLocker)
        {
            ReleaseExit();

            if (CanExecuteReleaseActions)
                if (silently)
                    ExecuteReleaseActionsSilently();
                else
                    ExecuteReleaseActions();
        }
    }

    private void ExecuteNonInterruptableActions(IEnumerable<CustomActionBase<T>> actions)
    {
        actions.Where(action => action != null).ToList().ForEach(ExecuteAction);
    }

    private void ExecuteNonInterruptableActionsSilently(IEnumerable<CustomActionBase<T>> actions)
    {
        actions.Where(action => action != null).ToList().ForEach(ExecuteActionSilently);
    }

    private void ExecuteAction(CustomActionBase<T> action)
    {
        if (_shutdownEvent.WaitOne(0))
            return;

        lock (_locker)
            CurrentAction = action;

        RaiseEvent(NewActionStarting);

        action.Execute();
    }

    /// <summary>
    /// Запускается в случае отмены операции пользователем
    /// В данном случае событие о начале выполнения очередного завершающего действия не
    /// генерируется, т.к. форма, отображающая данную информацию уже может быть
    /// разрушена
    /// </summary>
    /// <param name="action"></param>
    private void ExecuteActionSilently(CustomActionBase<T> action)
    {
        action.Execute();
    }

    public void Dispose()
    {
        _interruptableActions = null;
        _releaseActions = null;
        _initializeActions = null;
        _worker = null;
    }
}
В примере мы избавились от большого количества строк нетривиального кода. Вместо конструкции lock, можно использовать System.Collections.Concurrent. Но как показывают тесты, код с конструкцией lock работает в некоторых случаях быстрее. Всё же давайте упростим этот код ввиду его сложности на данном этапе, а поскольку темой статьи является рефакторинг, то будет изменять код по полной программе. Поскольку у нас используются неизменяемые коллекции, то они являются потокобезопасными. Вы можете, если сочтете нужным, оставить для себя предыдущий вариант, так как этот вариант добавит немного марафета коду. Конечный вариант, который у нас получился:
public sealed class ActionWorker<T> : IDisposable where T : class
{
    private Thread _worker;

    private ReadOnlyCollection<CustomActionBase<T>> _interruptableActions;
    private ReadOnlyCollection<CustomActionBase<T>> _releaseActions;
    private ReadOnlyCollection<CustomActionBase<T>> _initializeActions;

    private readonly ManualResetEvent _shutdownEvent;
    private readonly ManualResetEvent _pauseEvent;

    //Говорим компилятору не оптимизировать переменную _completed
    private volatile bool _completed;
    private int _releaseEnters;

    /// <summary>
    /// Действия завершены
    /// </summary>
    public event EventHandler ExecutionCompleted;

    /// <summary>
    /// Запускается новое действие
    /// </summary>
    public event EventHandler NewActionStarting;

    public CustomActionBase<T> CurrentAction { get; private set; }

    public Exception Exception { get; private set; }

    public ActionWorker(IEnumerable<CustomActionBase<T>> actions)
    {
        _shutdownEvent = new ManualResetEvent(false);
        _pauseEvent = new ManualResetEvent(true);
        InitializeAction(actions);
        _worker = new Thread(RunMethod);
    }

    private void InitializeAction(IEnumerable<CustomActionBase<T>> actions)
    {
        var actionsArray = actions.ToArray();
        _releaseActions = new ReadOnlyCollection<CustomActionBase<T>>(GetReleaseActions(actionsArray));
        _interruptableActions = new ReadOnlyCollection<CustomActionBase<T>>(GetNormalActions(actionsArray));
        _initializeActions = new ReadOnlyCollection<CustomActionBase<T>>(GetInitializeActions(actionsArray));
    }

    /// <summary>
    /// Запустить выполнение операций в отдельном потоке
    /// </summary>
    public void Start()
    {
        if (_completed)
            return;

        _worker.Start();
    }

    /// <summary>
    /// Продолжить выполнение после остановки
    /// </summary>
    public void Continue()
    {
        if (_completed)
            return;

        _pauseEvent.Set();
    }

    /// <summary>
    /// Приостановка выполнения
    /// </summary>
    public void Pause()
    {
        if (_completed)
            return;

        _pauseEvent.Reset();
    }

    /// <summary>
    /// Завершить выполнение операций
    /// </summary>
    public void Stop()
    {
        if (_completed)
            return;

        _shutdownEvent.Set();

        if(!_worker.Join(100))
            Application.DoEvents();
    }

    /// <summary>
    /// Возможность запуска действий завершения
    /// </summary>
    private bool CanExecuteReleaseActions
    {
        get
        {
            return _releaseEnters == 0;
        }
    }

    private void ReleaseEnter()
    {
        Interlocked.Increment(ref _releaseEnters);
    }

    private void ReleaseExit()
    {
        Interlocked.Decrement(ref _releaseEnters);
    }

    /// <summary>
    /// Получить последовательность действий
    /// </summary>
    /// <param name="actions"></param>
    /// <returns></returns>
    private static IList<CustomActionBase<T>> GetNormalActions(IEnumerable<CustomActionBase<T>> actions)
    {
        return GetActions(actions, CustomActionTypes.Normal);
    }

    /// <summary>
    /// Получить последовательность действий завершения
    /// </summary>
    /// <param name="actions"></param>
    /// <returns></returns>
    private static IList<CustomActionBase<T>> GetReleaseActions(IEnumerable<CustomActionBase<T>> actions)
    {
        return GetActions(actions, CustomActionTypes.Release);
    }

    /// <summary>
    /// Получить последовательность действий инициализации
    /// </summary>
    /// <param name="actions"></param>
    /// <returns></returns>
    private static IList<CustomActionBase<T>> GetInitializeActions(IEnumerable<CustomActionBase<T>> actions)
    {
        return GetActions(actions, CustomActionTypes.Initialize);
    }

    private static IList<CustomActionBase<T>> GetActions(IEnumerable<CustomActionBase<T>> actions, CustomActionTypes actionType)
    {
        return actions.Where(action => action.ActionType == actionType).ToList();
    }

    private void RunMethod()
    {
        while (true)
        {
            _pauseEvent.WaitOne(Timeout.Infinite);

            if (_shutdownEvent.WaitOne(0))
                break;

            Interlocked.Exchange(ref _releaseEnters, 0);

            ExecuteInitializeActions();

            ExecuteInterruptableActions();

            _completed = true;

            RaiseEvent(ExecutionCompleted);

            break;
        }
    }

    private void RaiseEvent(EventHandler handler)
    {
        if (handler == null)
            return;

        var mainForm = ApplicationSettings.MainForm;

        if (mainForm != null)
            ControlExtensions.Invoke(mainForm, handler, this, EventArgs.Empty);
    }

    private void ExecuteReleaseActions()
    {
        ExecuteActions(_releaseActions, ExecuteAction);
    }

    private void ExecuteReleaseActionsSilently()
    {
        ExecuteActions(_releaseActions, ExecuteActionSilently);
    }

    private void ExecuteInitializeActions()
    {
        ExecuteActions(_initializeActions, ExecuteAction);
    }

    private void ExecuteInterruptableActions()
    {
        ReleaseEnter();

        foreach (var action in _interruptableActions.TakeWhile(action => !_shutdownEvent.WaitOne(0)))
        {
            ReleaseEnter();

            using (var task = Task.Factory.StartNew(actionBase =>
                {
                    var localAction = (CustomActionBase<T>)actionBase;
                    ExecuteAction(localAction);
                }, action))
            {
                task.ContinueWith(result =>
                    {
                        ExecuteReleaseActionsSilently();
                        Exception = result.Exception;
                        RaiseEvent(ExecutionCompleted);
                    },
                    TaskContinuationOptions.OnlyOnFaulted);
                task.Wait();
            }

            OnActionCompleted();
        }

        ConditionallyExecuteReleaseActions(false);
    }

    public void OnActionCompleted()
    {
        ConditionallyExecuteReleaseActions(true);
    }

    /// <summary>
    /// Запуск действий завершения после последней выполняющейся операции
    /// </summary>
    /// <param name="silently">Тихий режим (без отображения на форме)</param>
    private void ConditionallyExecuteReleaseActions(bool silently)
    {
        ReleaseExit();

        if (CanExecuteReleaseActions)
            if (silently)
                ExecuteReleaseActionsSilently();
            else
                ExecuteReleaseActions();
    }

    private void ExecuteActions(IEnumerable<CustomActionBase<T>> actions,
        Action<CustomActionBase<T>> action)
    {
        foreach (var customActionBase in actions)
            action(customActionBase);
    }

    private void ExecuteAction(CustomActionBase<T> action)
    {
        if (_shutdownEvent.WaitOne(0))
            return;

        CurrentAction = action;

        RaiseEvent(NewActionStarting);

        action.Execute();
    }

    /// <summary>
    /// Запускается в случае отмены операции пользователем
    /// В данном случае событие о начале выполнения очередного завершающего действия не
    /// генерируется, т.к. форма, отображающая данную информацию уже может быть
    /// разрушена
    /// </summary>
    /// <param name="action"></param>
    private void ExecuteActionSilently(CustomActionBase<T> action)
    {
        action.Execute();
    }

    public void Dispose()
    {
        _interruptableActions = null;
        _releaseActions = null;
        _initializeActions = null;
        _worker = null;
    }
}

Итоги

В данной статье были рассмотрены типичные ошибки при проектировании многопоточного приложения. Если в подходе к исправлению данного многопоточного класса Вы увидели ошибки, буду рад услышать Ваше мнение и рассмотреть другой подход для решения этой задачи. Главное по возможности избегать Thread.Sleep.  

No comments:

Post a Comment