view Implab/Components/RunnableComponent.cs @ 254:12c00235b105 v3

Добавлена метка v3.0.1-beta для набора изменений 34df34841225
author cin
date Mon, 12 Feb 2018 17:03:49 +0300
parents 6f4630d0bcd9
children c52691faaf21
line wrap: on
line source

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace Implab.Components {
    /// <summary>
    /// Base class for implementing components which support start and stop operations,
    /// such components may represent running services.
    /// </summary>
    /// <remarks>
    /// This class provides a basic lifecycle from the creation to the
    /// termination of the component.
    /// </remarks>
    public class RunnableComponent : IRunnable, IInitializable, IDisposable {

        /// <summary>
        /// This class bounds <see cref="CancellationTokenSource"/> lifetime to the task,
        /// when the task completes the associated token source will be disposed.
        /// </summary>
        class AsyncOperationDescriptor {

            public static AsyncOperationDescriptor None { get; } = new AsyncOperationDescriptor();

            readonly CancellationTokenSource m_cts;

            bool m_done;

            public CancellationToken Token {
                get { return m_cts == null ? CancellationToken.None : m_cts.Token; }
            }

            public Task Task { get; private set; }

            private AsyncOperationDescriptor(Task task, CancellationTokenSource cts) {
                m_cts = cts;
                Task = Chain(task);
            }

            private AsyncOperationDescriptor() {
                Task = Task.CompletedTask;
            }

            public void Cancel() {
                if (m_cts != null) {
                    lock (m_cts) {
                        if (!m_done)
                            m_cts.Cancel();
                    }
                }
            }

            void Done() {
                if (m_cts != null) {
                    lock (m_cts) {
                        m_done = true;
                        m_cts.Dispose();
                    }
                } else {
                    m_done = true;
                }
            }

            async Task Chain(Task other) {
                try {
                    await other;
                } finally {
                    Done();
                }
            }

            public static AsyncOperationDescriptor Create(Func<CancellationToken, Task> factory, CancellationToken ct) {
                var cts = ct.CanBeCanceled ?
                    CancellationTokenSource.CreateLinkedTokenSource(ct) :
                    new CancellationTokenSource();

                return new AsyncOperationDescriptor(factory(cts.Token), cts);
            }

        }

        // this lock is used to synchronize state flow of the component during
        // completions or the operations.
        readonly object m_lock = new object();

        // current operation cookie, used to check wheather a call to
        // MoveSuccess/MoveFailed method belongs to the current
        // operation, if cookies didn't match ignore completion result.
        object m_cookie;

        AsyncOperationDescriptor m_current = AsyncOperationDescriptor.None;

        ExecutionState m_state;


        protected RunnableComponent(bool initialized) {
            State = initialized ? ExecutionState.Ready : ExecutionState.Created;
        }

        public Task Completion {
            get { return m_current.Task; }
        }

        public ExecutionState State {
            get { return m_state; }
            private set {
                if (m_state != value) {
                    m_state = value;
                    StateChanged.DispatchEvent(this, new StateChangeEventArgs {
                        State = value,
                        LastError = LastError
                    });
                }
            }
        }

        public Exception LastError { get; private set; }

        public event EventHandler<StateChangeEventArgs> StateChanged;

        /// <summary>
        /// Releases all resources used by the current component regardless of its
        /// execution state.
        /// </summary>
        /// <remarks>
        /// Calling to this method may result unexpedted results if the component
        /// isn't in the stopped state. Call this method after the component is
        /// stopped if needed or if the component is in the failed state.
        /// </remarks>
        public void Dispose() {
            bool dispose = false;
            if (dispose) {
                Dispose(true);
                GC.SuppressFinalize(this);
            }
        }

        ~RunnableComponent() {
            Dispose(false);
        }

        /// <summary>
        /// Releases all resources used by the current component regardless of its
        /// execution state.
        /// </summary>
        /// <param name="disposing">Indicates that the component is disposed
        /// during a normal disposing or during GC.</param>
        protected virtual void Dispose(bool disposing) {
        }

        public void Initialize() {
            var cookie = new object();
            if (MoveInitialize(cookie))
                ScheduleTask(InitializeInternalAsync, CancellationToken.None, cookie);
        }

        /// <summary>
        /// This method is used for initialization during a component creation.
        /// </summary>
        /// <param name="ct">A cancellation token for this operation</param>
        /// <remarks>
        /// This method should be used for short and mostly syncronous operations,
        /// other operations which require time to run shoud be placed in
        /// <see cref="StartInternalAsync(CancellationToken)"/> method.
        /// </remarks>
        protected virtual Task InitializeInternalAsync(CancellationToken ct) {
            return Task.CompletedTask;
        }

        public void Start(CancellationToken ct) {
            var cookie = new object();
            if (MoveStart(cookie))
                ScheduleTask(StartInternalAsync, ct, cookie);
        }

        protected virtual Task StartInternalAsync(CancellationToken ct) {
            return Task.CompletedTask;
        }

        public void Stop(CancellationToken ct) {
            var cookie = new object();
            if (MoveStop(cookie))
                ScheduleTask(StopAsync, ct, cookie);
        }

        async Task StopAsync(CancellationToken ct) {
            m_current.Cancel();
            await Completion;

            ct.ThrowIfCancellationRequested();

            await StopInternalAsync(ct);
        }

        protected virtual Task StopInternalAsync(CancellationToken ct) {
            return Task.CompletedTask;
        }


        #region state management

        bool MoveInitialize(object cookie) {
            lock (m_lock) {
                if (State != ExecutionState.Created)
                    return false;
                State = ExecutionState.Initializing;
                m_cookie = cookie;
                return true;
            }
        }

        bool MoveStart(object cookie) {
            lock (m_lock) {
                if (State != ExecutionState.Ready)
                    return false;
                State = ExecutionState.Starting;
                m_cookie = cookie;
                return true;
            }
        }

        bool MoveStop(object cookie) {
            lock (m_lock) {
                if (State != ExecutionState.Starting && State != ExecutionState.Running)
                    return false;
                State = ExecutionState.Stopping;
                m_cookie = cookie;
                return true;
            }
        }

        void MoveSuccess(object cookie) {
            lock (m_lock) {
                if (m_cookie != cookie)
                    return;
                switch (State) {
                    case ExecutionState.Initializing:
                        State = ExecutionState.Ready;
                        break;
                    case ExecutionState.Starting:
                        State = ExecutionState.Running;
                        break;
                    case ExecutionState.Stopping:
                        State = ExecutionState.Stopped;
                        break;
                }
            }
        }

        void MoveFailed(Exception err, object cookie) {
            lock (m_lock) {
                if (m_cookie != cookie)
                    return;
                LastError = err;
                State = ExecutionState.Failed;
            }
        }



        protected async void ScheduleTask(Func<CancellationToken, Task> next, CancellationToken ct, object cookie) {
            try {
                m_current = AsyncOperationDescriptor.Create(next, ct);
                await m_current.Task;
                MoveSuccess(cookie);
            } catch (Exception e) {
                MoveFailed(e, cookie);
            }
        }

        #endregion
    }
}