Mercurial > pub > ImplabNet
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 } }