changeset 224:1ba2127cfcd8 v2

Слияние с default
author cin
date Tue, 22 Aug 2017 12:45:01 +0300
parents 8808383fcb94 (diff) 27ea7f07e2e4 (current diff)
children 8222a2ab3ab7
files Implab/Implab.csproj
diffstat 54 files changed, 2115 insertions(+), 569 deletions(-) [+]
line wrap: on
line diff
--- a/Implab.Diagnostics.Interactive/InteractiveListener.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Diagnostics.Interactive/InteractiveListener.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -17,7 +17,6 @@
         readonly Promise m_guiStarted = new Promise();
         
         readonly IPromise m_guiFinished;
-        //        readonly IPromise m_workerFinished = new Promise<object>();
         
         readonly MTQueue<TraceViewItem> m_queue = new MTQueue<TraceViewItem>();
         readonly AutoResetEvent m_queueEvent = new AutoResetEvent(false);
@@ -30,8 +29,8 @@
         readonly ManualResetEvent m_pauseEvent = new ManualResetEvent(true);
 
         public InteractiveListener() {
-            m_guiFinished = AsyncPool.RunThread(GuiThread);
-            /*m_workerFinished = */AsyncPool.RunThread(QueueThread);
+            m_guiFinished = RunGuiThread();
+            AsyncPool.RunThread(QueueThread);
 
             m_guiStarted.Join();
         }
@@ -56,13 +55,38 @@
                 if (m_queue.TryDequeue(out item)) {
                     Interlocked.Decrement(ref m_queueLength);
 
-                    m_syncGuiThread.Send(x => m_form.AddTraceEvent(item),null);
+                    m_syncGuiThread.Post(x => m_form.AddTraceEvent(item),null);
                 } else {
                     m_queueEvent.WaitOne();
                 }
             }
         }
 
+        public IPromise RunGuiThread() {
+            var p = new Promise();
+
+            var caller = TraceContext.Instance.CurrentOperation;
+
+            var worker = new Thread(() => {
+                TraceContext.Instance.EnterLogicalOperation(caller, false);
+                try {
+                    Application.OleRequired();
+                    GuiThread();
+                    p.Resolve();
+                } catch (Exception e) {
+                    p.Reject(e);
+                } finally {
+                    TraceContext.Instance.Leave();
+                }
+            });
+            worker.SetApartmentState(ApartmentState.STA);
+            worker.IsBackground = true;
+            worker.Name = string.Format("{0} GUI Thread", nameof(InteractiveListener));
+            worker.Start();
+
+            return p;
+        }
+
         public void Pause() {
             // for consistency we need to set this properties atomically
             lock (m_pauseLock) {
@@ -112,8 +136,9 @@
                 Indent = args.Operation.Level,
                 Message = entry.ToString(),
                 Thread = args.ThreadId,
-                Channel = args.ChannelName,
-                Timestamp = Environment.TickCount
+                Channel = args.Channel.ToString(),
+                Timestamp = Environment.TickCount,
+                TimeDelta = args.OperationTimeOffset
             };
 
             Enqueue(item);
--- a/Implab.Diagnostics.Interactive/TraceForm.Designer.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Diagnostics.Interactive/TraceForm.Designer.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -23,13 +23,14 @@
         /// the contents of this method with the code editor.
         /// </summary>
         private void InitializeComponent() {
-            this.components = new System.ComponentModel.Container();
             System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle1 = new System.Windows.Forms.DataGridViewCellStyle();
-            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle4 = new System.Windows.Forms.DataGridViewCellStyle();
             System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle2 = new System.Windows.Forms.DataGridViewCellStyle();
+            System.Windows.Forms.DataGridViewCellStyle dataGridViewCellStyle3 = new System.Windows.Forms.DataGridViewCellStyle();
             this.eventsDataGrid = new System.Windows.Forms.DataGridView();
-            this.traceViewItemBindingSource = new System.Windows.Forms.BindingSource(this.components);
+            this.traceViewItemBindingSource = new System.Windows.Forms.BindingSource();
             this.threadDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
+            this.TimeDelta = new System.Windows.Forms.DataGridViewTextBoxColumn();
             this.Channel = new System.Windows.Forms.DataGridViewTextBoxColumn();
             this.messageDataGridViewTextBoxColumn = new System.Windows.Forms.DataGridViewTextBoxColumn();
             ((System.ComponentModel.ISupportInitialize)(this.eventsDataGrid)).BeginInit();
@@ -56,20 +57,22 @@
             this.eventsDataGrid.ColumnHeadersHeightSizeMode = System.Windows.Forms.DataGridViewColumnHeadersHeightSizeMode.AutoSize;
             this.eventsDataGrid.Columns.AddRange(new System.Windows.Forms.DataGridViewColumn[] {
             this.threadDataGridViewTextBoxColumn,
+            this.TimeDelta,
             this.Channel,
             this.messageDataGridViewTextBoxColumn});
             this.eventsDataGrid.DataSource = this.traceViewItemBindingSource;
-            dataGridViewCellStyle3.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
-            dataGridViewCellStyle3.BackColor = System.Drawing.SystemColors.Window;
-            dataGridViewCellStyle3.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
-            dataGridViewCellStyle3.ForeColor = System.Drawing.SystemColors.ControlText;
-            dataGridViewCellStyle3.SelectionBackColor = System.Drawing.SystemColors.Highlight;
-            dataGridViewCellStyle3.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
-            dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
-            this.eventsDataGrid.DefaultCellStyle = dataGridViewCellStyle3;
+            dataGridViewCellStyle4.Alignment = System.Windows.Forms.DataGridViewContentAlignment.MiddleLeft;
+            dataGridViewCellStyle4.BackColor = System.Drawing.SystemColors.Window;
+            dataGridViewCellStyle4.Font = new System.Drawing.Font("Lucida Console", 8.25F, System.Drawing.FontStyle.Regular, System.Drawing.GraphicsUnit.Point, ((byte)(204)));
+            dataGridViewCellStyle4.ForeColor = System.Drawing.SystemColors.ControlText;
+            dataGridViewCellStyle4.SelectionBackColor = System.Drawing.SystemColors.Highlight;
+            dataGridViewCellStyle4.SelectionForeColor = System.Drawing.SystemColors.HighlightText;
+            dataGridViewCellStyle4.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
+            this.eventsDataGrid.DefaultCellStyle = dataGridViewCellStyle4;
             this.eventsDataGrid.Location = new System.Drawing.Point(12, 12);
             this.eventsDataGrid.Name = "eventsDataGrid";
             this.eventsDataGrid.ReadOnly = true;
+            this.eventsDataGrid.RowHeadersVisible = false;
             this.eventsDataGrid.RowHeadersWidth = 17;
             this.eventsDataGrid.RowTemplate.Resizable = System.Windows.Forms.DataGridViewTriState.False;
             this.eventsDataGrid.Size = new System.Drawing.Size(939, 480);
@@ -82,31 +85,43 @@
             // 
             // threadDataGridViewTextBoxColumn
             // 
-            this.threadDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCellsExceptHeader;
+            this.threadDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.AllCells;
             this.threadDataGridViewTextBoxColumn.DataPropertyName = "Thread";
             this.threadDataGridViewTextBoxColumn.HeaderText = "TID";
             this.threadDataGridViewTextBoxColumn.Name = "threadDataGridViewTextBoxColumn";
             this.threadDataGridViewTextBoxColumn.ReadOnly = true;
-            this.threadDataGridViewTextBoxColumn.Width = 5;
+            this.threadDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
+            this.threadDataGridViewTextBoxColumn.Width = 32;
+            // 
+            // TimeDelta
+            // 
+            this.TimeDelta.DataPropertyName = "TimeDelta";
+            dataGridViewCellStyle2.Format = "\'+\' 0 \'ms\'";
+            dataGridViewCellStyle2.NullValue = null;
+            this.TimeDelta.DefaultCellStyle = dataGridViewCellStyle2;
+            this.TimeDelta.HeaderText = "TimeDelta";
+            this.TimeDelta.Name = "TimeDelta";
+            this.TimeDelta.ReadOnly = true;
+            this.TimeDelta.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
             // 
             // Channel
             // 
-            this.Channel.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.DisplayedCells;
             this.Channel.DataPropertyName = "Channel";
             this.Channel.HeaderText = "Channel";
             this.Channel.Name = "Channel";
             this.Channel.ReadOnly = true;
-            this.Channel.Width = 79;
+            this.Channel.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
             // 
             // messageDataGridViewTextBoxColumn
             // 
             this.messageDataGridViewTextBoxColumn.AutoSizeMode = System.Windows.Forms.DataGridViewAutoSizeColumnMode.Fill;
             this.messageDataGridViewTextBoxColumn.DataPropertyName = "FormattedMessage";
-            dataGridViewCellStyle2.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
-            this.messageDataGridViewTextBoxColumn.DefaultCellStyle = dataGridViewCellStyle2;
+            dataGridViewCellStyle3.WrapMode = System.Windows.Forms.DataGridViewTriState.False;
+            this.messageDataGridViewTextBoxColumn.DefaultCellStyle = dataGridViewCellStyle3;
             this.messageDataGridViewTextBoxColumn.HeaderText = "Message";
             this.messageDataGridViewTextBoxColumn.Name = "messageDataGridViewTextBoxColumn";
             this.messageDataGridViewTextBoxColumn.ReadOnly = true;
+            this.messageDataGridViewTextBoxColumn.SortMode = System.Windows.Forms.DataGridViewColumnSortMode.NotSortable;
             // 
             // TraceForm
             // 
@@ -127,8 +142,8 @@
         private System.Windows.Forms.DataGridView eventsDataGrid;
         private System.Windows.Forms.BindingSource traceViewItemBindingSource;
         private System.Windows.Forms.DataGridViewTextBoxColumn threadDataGridViewTextBoxColumn;
+        private System.Windows.Forms.DataGridViewTextBoxColumn TimeDelta;
         private System.Windows.Forms.DataGridViewTextBoxColumn Channel;
         private System.Windows.Forms.DataGridViewTextBoxColumn messageDataGridViewTextBoxColumn;
-
     }
 }
\ No newline at end of file
--- a/Implab.Diagnostics.Interactive/TraceForm.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Diagnostics.Interactive/TraceForm.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -31,7 +31,8 @@
 
         public void AddTraceEvent(TraceViewItem item) {
             traceViewItemBindingSource.Add(item);
-            eventsDataGrid.FirstDisplayedScrollingRowIndex = eventsDataGrid.RowCount - 1;
+            if(eventsDataGrid.RowCount > 0)
+                eventsDataGrid.FirstDisplayedScrollingRowIndex = eventsDataGrid.RowCount - 1;
         }
 
         Color GetThreadColor(int thread) {
--- a/Implab.Diagnostics.Interactive/TraceForm.resx	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Diagnostics.Interactive/TraceForm.resx	Tue Aug 22 12:45:01 2017 +0300
@@ -117,6 +117,9 @@
   <resheader name="writer">
     <value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
   </resheader>
+  <metadata name="TimeDelta.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
+    <value>True</value>
+  </metadata>
   <metadata name="Channel.UserAddedColumn" type="System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089">
     <value>True</value>
   </metadata>
--- a/Implab.Diagnostics.Interactive/TraceViewItem.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Diagnostics.Interactive/TraceViewItem.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -9,6 +9,7 @@
         string m_formattedValue;
 
         public string Message { get; set; }
+        public int TimeDelta { get; set; }
         public int Timestamp { get; set; }
         public int Indent { get; set; }
         public int Thread { get; set; }
--- a/Implab.Fx.Test/Implab.Fx.Test.csproj	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Fx.Test/Implab.Fx.Test.csproj	Tue Aug 22 12:45:01 2017 +0300
@@ -80,6 +80,7 @@
     <Compile Include="Sample\OverlayForm.Designer.cs">
       <DependentUpon>OverlayForm.cs</DependentUpon>
     </Compile>
+    <Compile Include="StaApartmentTests.cs" />
   </ItemGroup>
   <ItemGroup>
     <EmbeddedResource Include="Sample\MainForm.resx">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab.Fx.Test/StaApartmentTests.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,52 @@
+using System;
+using System.Reflection;
+using System.Threading;
+using Implab.Parallels;
+using Implab.Components;
+
+#if MONO
+
+using NUnit.Framework;
+using TestClassAttribute = NUnit.Framework.TestFixtureAttribute;
+using TestMethodAttribute = NUnit.Framework.TestAttribute;
+using AssertFailedException = NUnit.Framework.AssertionException;
+#else
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+#endif
+namespace Implab.Fx.Test {
+    [TestClass]
+    public class StaApartmentTests {
+        [TestMethod]
+        public void CreateDestroyApartment() {
+            var apartment = new StaApartment();
+            try {
+                Assert.IsNotNull(apartment.SyncContext);
+                Assert.Fail();
+            } catch (InvalidOperationException) {
+                // OK
+            }
+
+            apartment.Start().Join();
+            Assert.AreEqual(apartment.State, ExecutionState.Running);
+
+            Assert.IsNotNull(apartment.SyncContext);
+            apartment.Stop().Join();
+            
+            Assert.IsTrue(apartment.State == ExecutionState.Disposed);
+        }
+
+        [TestMethod]
+        public void InvokeInApartment() {
+            var apartment = new StaApartment();
+
+            apartment.Start().Join();
+
+            var apType = apartment.Invoke(() => { return Thread.CurrentThread.GetApartmentState(); }).Join();
+            Assert.AreEqual(apType, ApartmentState.STA);
+
+            apartment.Stop().Join();
+        }
+    }
+}
--- a/Implab.Fx/Implab.Fx.csproj	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Fx/Implab.Fx.csproj	Tue Aug 22 12:45:01 2017 +0300
@@ -70,6 +70,7 @@
     <Compile Include="PromiseHelpers.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="ControlBoundPromise.cs" />
+    <Compile Include="StaApartment.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Implab\Implab.csproj">
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab.Fx/StaApartment.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,204 @@
+using Implab.Components;
+using Implab.Diagnostics;
+using Implab.Parallels;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using System.Windows.Forms;
+
+namespace Implab.Fx {
+    public class StaApartment : RunnableComponent {
+        readonly Thread m_worker;
+        SynchronizationContext m_syncContext;
+        SyncContextPromise m_enterPromise;
+
+        readonly Promise m_threadStarted;
+        readonly Promise m_threadTerminated;
+
+        public StaApartment() : base(true) {
+            m_threadStarted = new Promise();
+            m_threadTerminated = new Promise();
+
+            m_worker = new Thread(WorkerEntry);
+            m_worker.SetApartmentState(ApartmentState.STA);
+            m_worker.IsBackground = true;
+            m_worker.Name = "STA managed aparment";
+        }
+
+        public SynchronizationContext SyncContext {
+            get {
+                if (m_syncContext == null)
+                    throw new InvalidOperationException();
+                return m_syncContext;
+            }
+        }
+
+        /// <summary>
+        /// Returns the promise which will dispatch all handlers inside the apartment using it's <see cref="SynchronizationContext"/>
+        /// </summary>
+        /// <remarks>
+        /// Current implementation is optimized and will lost aync operation stack
+        /// </remarks>
+        /// <returns>The promise</returns>
+        public IPromise Enter() {
+            if (m_enterPromise == null)
+                throw new InvalidOperationException();
+            return m_enterPromise;
+        }
+
+        public IPromise Invoke(Action<ICancellationToken> action) {
+            Safe.ArgumentNotNull(action, "action");
+
+            if (m_syncContext == null)
+                throw new InvalidOperationException();
+            var p = new Promise();
+            var lop = TraceContext.Instance.CurrentOperation;
+
+            m_syncContext.Post(x => {
+                TraceContext.Instance.EnterLogicalOperation(lop, false);
+                try {
+                    if (p.CancelOperationIfRequested())
+                        return;
+
+                    action(p);
+                    p.Resolve();
+                } catch (Exception e) {
+                    p.Reject(e);
+                } finally {
+                    TraceContext.Instance.Leave();
+                }
+            }, null);
+
+            return p;
+        }
+
+        public IPromise<T> Invoke<T>(Func<ICancellationToken, T> action) {
+            Safe.ArgumentNotNull(action, "action");
+
+            if (m_syncContext == null)
+                throw new InvalidOperationException();
+            var p = new Promise<T>();
+            var lop = TraceContext.Instance.CurrentOperation;
+
+            m_syncContext.Post(x => {
+                TraceContext.Instance.EnterLogicalOperation(lop, false);
+                try {
+                    if (p.CancelOperationIfRequested())
+                        return;
+                    p.Resolve(action(p));
+                } catch (Exception e) {
+                    p.Reject(e);
+                } finally {
+                    TraceContext.Instance.Leave();
+                }
+            }, null);
+
+            return p;
+        }
+
+        public IPromise Invoke(Action action) {
+            Safe.ArgumentNotNull(action, "action");
+
+            if (m_syncContext == null)
+                throw new InvalidOperationException();
+            var p = new Promise();
+            var lop = TraceContext.Instance.CurrentOperation;
+
+            m_syncContext.Post(x => {
+                TraceContext.Instance.EnterLogicalOperation(lop, false);
+                try {
+                    if (p.CancelOperationIfRequested())
+                        return;
+                    action();
+                    p.Resolve();
+                } catch (Exception e) {
+                    p.Reject(e);
+                } finally {
+                    TraceContext.Instance.Leave();
+                }
+            }, null);
+
+            return p;
+        }
+
+        public IPromise<T> Invoke<T>(Func<T> action) {
+            Safe.ArgumentNotNull(action, "action");
+
+            if (m_syncContext == null)
+                throw new InvalidOperationException();
+            var p = new Promise<T>();
+            var lop = TraceContext.Instance.CurrentOperation;
+
+            m_syncContext.Post(x => {
+                TraceContext.Instance.EnterLogicalOperation(lop, false);
+                try {
+                    if (p.CancelOperationIfRequested())
+                        return;
+                    p.Resolve(action());
+                } catch (Exception e) {
+                    p.Reject(e);
+                } finally {
+                    TraceContext.Instance.Leave();
+                }
+            }, null);
+
+            return p;
+        }
+
+
+        /// <summary>
+        /// Starts the apartment thread
+        /// </summary>
+        /// <returns>Promise which will be fullfiled when the syncronization
+        /// context will be ready to accept tasks.</returns>
+        protected override IPromise OnStart() {
+            m_worker.Start();
+            return m_threadStarted;
+        }
+
+        /// <summary>
+        /// Posts quit message to the message loop of the apartment
+        /// </summary>
+        /// <returns>Promise</returns>
+        protected override IPromise OnStop() {
+            m_syncContext.Post(x => Application.ExitThread(), null);
+            return m_threadTerminated;
+        }
+
+        void WorkerEntry() {
+            m_syncContext = new WindowsFormsSynchronizationContext();
+            SynchronizationContext.SetSynchronizationContext(m_syncContext);
+            m_enterPromise = new SyncContextPromise(m_syncContext);
+            m_threadStarted.Resolve();
+            m_enterPromise.Resolve();
+
+            Application.OleRequired();
+            Application.Run();
+
+            try {
+                OnShutdown();
+                m_threadTerminated.Resolve();
+            } catch(Exception err) {
+                m_threadTerminated.Reject(err);
+            }
+        }
+
+        /// <summary>
+        /// Called from the STA apartment after the message loop is terminated, override this
+        /// method to handle apartment cleanup.
+        /// </summary>
+        protected virtual void OnShutdown() {
+        }
+
+        protected override void Dispose(bool disposing) {
+            if (disposing) {
+                if (!m_threadTerminated.IsResolved)
+                    m_syncContext.Post(x => Application.ExitThread(), null);
+            }
+            base.Dispose(disposing);
+        }
+    }
+}
--- a/Implab.Test/AsyncTests.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Test/AsyncTests.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -336,7 +336,7 @@
                     Console.WriteLine("done reader #2: {0} ms", Environment.TickCount - t1);
                 }
             )
-                .Bundle()
+                .PromiseAll()
                 .Join();
 
             Assert.AreEqual(count * 3, res1 + res2);
@@ -414,7 +414,7 @@
                     Console.WriteLine("done reader #2: {0} ms", Environment.TickCount - t1);
                 }
             )
-                .Bundle()
+                .PromiseAll()
                 .Join();
 
             Assert.AreEqual(summ , r1 + r2);
@@ -490,7 +490,7 @@
                     Console.WriteLine("done reader #2: {0} ms, avg chunk size: {1}", Environment.TickCount - t1, avgchunk);
                 }
             )
-                .Bundle()
+                .PromiseAll()
                 .Join();
 
             Assert.AreEqual(summ , r1 + r2);
@@ -593,7 +593,7 @@
                     Console.WriteLine("done reader #2: {0} ms, {1} items", Environment.TickCount - t1, count);
                 }
             )
-                .Bundle()
+                .PromiseAll()
                 .Join();
 
             Assert.AreEqual(summ , r1 + r2);
@@ -835,7 +835,7 @@
                             log.Enqueue("Writer #1 lock released");
                         }
                     }
-                ).Bundle().Join(1000);
+                ).PromiseAll().Join(1000);
                 log.Enqueue("Done");
             } catch(Exception error) {
                 log.Enqueue(error.Message);
--- a/Implab.Test/Implab.Format.Test/Implab.Format.Test.csproj	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Test/Implab.Format.Test/Implab.Format.Test.csproj	Tue Aug 22 12:45:01 2017 +0300
@@ -1,52 +1,55 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.30703</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{4D364996-7ECD-4193-8F90-F223FFEA49DA}</ProjectGuid>
-    <OutputType>Library</OutputType>
-    <RootNamespace>Implab.Format.Test</RootNamespace>
-    <AssemblyName>Implab.Format.Test</AssemblyName>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
-    <ReleaseVersion>0.2</ReleaseVersion>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug</OutputPath>
-    <DefineConstants>DEBUG;</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <DebugType>full</DebugType>
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release</OutputPath>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="nunit.framework">
-      <HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
-    </Reference>
-  </ItemGroup>
-  <ItemGroup>
-    <Compile Include="JsonTests.cs" />
-  </ItemGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-  <ItemGroup>
-    <ProjectReference Include="..\..\Implab\Implab.csproj">
-      <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project>
-      <Name>Implab</Name>
-    </ProjectReference>
-  </ItemGroup>
-  <ItemGroup>
-    <None Include="packages.config" />
-  </ItemGroup>
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.30703</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{4D364996-7ECD-4193-8F90-F223FFEA49DA}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <RootNamespace>Implab.Format.Test</RootNamespace>
+    <AssemblyName>Implab.Format.Test</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <ReleaseVersion>0.2</ReleaseVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <DebugType>full</DebugType>
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="System" />
+    <Reference Include="nunit.framework">
+      <HintPath>..\..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
+    </Reference>
+  </ItemGroup>
+  <ItemGroup>
+    <Compile Include="JsonTests.cs" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <ProjectReference Include="..\..\Implab\Implab.csproj">
+      <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project>
+      <Name>Implab</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
 </Project>
\ No newline at end of file
--- a/Implab.Test/Implab.Test.csproj	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Test/Implab.Test.csproj	Tue Aug 22 12:45:01 2017 +0300
@@ -63,6 +63,9 @@
   <ItemGroup>
     <Compile Include="AsyncTests.cs" />
     <Compile Include="CancelationTests.cs" />
+    <Compile Include="Mock\MockPollingComponent.cs" />
+    <Compile Include="Mock\MockRunnableComponent.cs" />
+    <Compile Include="PollingComponentTests.cs" />
     <Compile Include="PromiseHelper.cs" />
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="RunnableComponentTests.cs" />
@@ -73,6 +76,9 @@
       <Name>Implab</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Implab.Format.Test\" />
+  </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <!-- To modify your build process, add your task inside one of the targets below and uncomment it. 
        Other similar extension points exist, see Microsoft.Common.targets.
--- a/Implab.Test/Implab.Test.mono.csproj	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Test/Implab.Test.mono.csproj	Tue Aug 22 12:45:01 2017 +0300
@@ -1,69 +1,81 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-  <PropertyGroup>
-    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
-    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
-    <ProductVersion>8.0.30703</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
-    <ProjectGuid>{2BD05F84-E067-4B87-9477-FDC2676A21C6}</ProjectGuid>
-    <OutputType>Library</OutputType>
-    <RootNamespace>Implab.Test</RootNamespace>
-    <AssemblyName>Implab.Test</AssemblyName>
-    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
-    <ReleaseVersion>0.2</ReleaseVersion>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug</OutputPath>
-    <DefineConstants>DEBUG;MONO</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release</OutputPath>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-    <DefineConstants>MONO</DefineConstants>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug 4.5|AnyCPU' ">
-    <DebugSymbols>true</DebugSymbols>
-    <DebugType>full</DebugType>
-    <Optimize>false</Optimize>
-    <OutputPath>bin\Debug</OutputPath>
-    <DefineConstants>DEBUG;TRACE;NET_4_5;MONO</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-  </PropertyGroup>
-  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release 4.5|AnyCPU' ">
-    <Optimize>true</Optimize>
-    <OutputPath>bin\Release</OutputPath>
-    <DefineConstants>NET_4_5;MONO</DefineConstants>
-    <ErrorReport>prompt</ErrorReport>
-    <WarningLevel>4</WarningLevel>
-    <ConsolePause>false</ConsolePause>
-  </PropertyGroup>
-  <ItemGroup>
-    <Reference Include="System" />
-    <Reference Include="nunit.framework" />
-  </ItemGroup>
-  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
-  <ItemGroup>
-    <Compile Include="AsyncTests.cs" />
-    <Compile Include="PromiseHelper.cs" />
-    <Compile Include="Properties\AssemblyInfo.cs" />
-    <Compile Include="CancelationTests.cs" />
-    <Compile Include="RunnableComponentTests.cs" />
-  </ItemGroup>
-  <ItemGroup>
-    <ProjectReference Include="..\Implab\Implab.csproj">
-      <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project>
-      <Name>Implab</Name>
-    </ProjectReference>
-  </ItemGroup>
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+    <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
+    <ProductVersion>8.0.30703</ProductVersion>
+    <SchemaVersion>2.0</SchemaVersion>
+    <ProjectGuid>{2BD05F84-E067-4B87-9477-FDC2676A21C6}</ProjectGuid>
+    <OutputType>Library</OutputType>
+    <RootNamespace>Implab.Test</RootNamespace>
+    <AssemblyName>Implab.Test</AssemblyName>
+    <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+    <ReleaseVersion>0.2</ReleaseVersion>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;MONO</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+    <DefineConstants>MONO</DefineConstants>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug 4.5|AnyCPU' ">
+    <DebugSymbols>true</DebugSymbols>
+    <DebugType>full</DebugType>
+    <Optimize>false</Optimize>
+    <OutputPath>bin\Debug</OutputPath>
+    <DefineConstants>DEBUG;TRACE;NET_4_5;MONO</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release 4.5|AnyCPU' ">
+    <Optimize>true</Optimize>
+    <OutputPath>bin\Release</OutputPath>
+    <DefineConstants>NET_4_5;MONO</DefineConstants>
+    <ErrorReport>prompt</ErrorReport>
+    <WarningLevel>4</WarningLevel>
+    <ConsolePause>false</ConsolePause>
+  </PropertyGroup>
+  <ItemGroup>
+    <Reference Include="nunit.framework, Version=2.6.4.14350, Culture=neutral, PublicKeyToken=96d09a1eb7f44a77, processorArchitecture=MSIL">
+      <HintPath>..\packages\NUnit.2.6.4\lib\nunit.framework.dll</HintPath>
+      <Private>True</Private>
+    </Reference>
+    <Reference Include="System" />
+  </ItemGroup>
+  <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+  <ItemGroup>
+    <Compile Include="AsyncTests.cs" />
+    <Compile Include="PromiseHelper.cs" />
+    <Compile Include="Properties\AssemblyInfo.cs" />
+    <Compile Include="CancelationTests.cs" />
+    <Compile Include="RunnableComponentTests.cs" />
+    <Compile Include="PollingComponentTests.cs" />
+    <Compile Include="Mock\MockRunnableComponent.cs" />
+    <Compile Include="Mock\MockPollingComponent.cs" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\Implab\Implab.csproj">
+      <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project>
+      <Name>Implab</Name>
+    </ProjectReference>
+  </ItemGroup>
+  <ItemGroup>
+    <Service Include="{82A7F48D-3B50-4B1E-B82E-3ADA8210C358}" />
+  </ItemGroup>
+  <ItemGroup>
+    <None Include="packages.config" />
+  </ItemGroup>
 </Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab.Test/Mock/MockPollingComponent.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,71 @@
+using System;
+using Implab.Components;
+
+namespace Implab.Test.Mock {
+    class MockPollingComponent : PollingComponent {
+        public MockPollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, IPromise> dispatcher, bool initialized) : base(interval, dispatcher, initialized)  {
+        }
+
+        public Action MockInit {
+            get;
+            set;
+        }
+
+        public Action<Exception> MockOnError {
+            get;
+            set;
+        }
+
+        public Action<Exception> MockOnCancel {
+            get;
+            set;
+        }
+
+        public Func<IPromise> MockStart {
+            get;
+            set;
+        }
+
+        public Func<IPromise> MockStop {
+            get;
+            set;
+        }
+
+        public Func<ICancellationToken, IPromise> MockTick {
+            get;
+            set;
+        }
+
+        protected override IPromise OnStart() {
+            return MockStart != null ? Safe.Run(MockStart).Chain(base.OnStart) : Safe.Run(base.OnStart);
+        }
+
+        protected override IPromise OnStop() {
+            return MockStop != null ? Safe.Run(MockStop).Chain(base.OnStop) : Safe.Run(base.OnStop);
+        }
+
+        protected override void OnInitialize() {
+            if (MockInit != null)
+                MockInit();
+        }
+
+        protected override IPromise OnTick(ICancellationToken cancellationToken) {
+            return MockTick != null ? Safe.Run(() => MockTick(cancellationToken)) : Promise.Success;
+        }
+
+        protected override void OnTickCancel(Exception error) {
+            if (MockOnCancel != null)
+                MockOnCancel(error);
+        }
+
+        protected override void OnTickError(Exception error) {
+            if (MockOnError != null)
+                MockOnError(error);
+        }
+
+        public void CallComponentFail(Exception error) {
+            Fail(error);
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab.Test/Mock/MockRunnableComponent.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,52 @@
+using System;
+using Implab.Components;
+
+namespace Implab.Test.Mock {
+    class MockRunnableComponent : RunnableComponent {
+        public MockRunnableComponent(bool initialized) : base(initialized) {
+        }
+
+        public MockRunnableComponent(bool initialized, bool reusable) : base(initialized, reusable) {
+        }
+
+        public Action MockInit {
+            get;
+            set;
+        }
+
+        public Func<IPromise> MockStart {
+            get;
+            set;
+        }
+
+        public Func<IPromise> MockStop {
+            get;
+            set;
+        }
+
+        public Action<bool> MockDispose {
+            get;
+            set;
+        }
+
+        protected override IPromise OnStart() {
+            return MockStart != null ? Safe.Run(MockStart).Chain(base.OnStart) : Safe.Run(base.OnStart);
+        }
+
+        protected override IPromise OnStop() {
+            return MockStop != null ? Safe.Run(MockStop).Chain(base.OnStop) : Safe.Run(base.OnStop);
+        }
+
+        protected override void OnInitialize() {
+            if (MockInit != null)
+                MockInit();
+        }
+
+        protected override void Dispose(bool disposing) {
+            if (MockDispose != null)
+                MockDispose(disposing);
+            base.Dispose(disposing);
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab.Test/PollingComponentTests.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,230 @@
+using System;
+using System.Reflection;
+using System.Threading;
+using Implab.Parallels;
+using Implab.Components;
+using Implab.Test.Mock;
+
+#if MONO
+
+using NUnit.Framework;
+using TestClassAttribute = NUnit.Framework.TestFixtureAttribute;
+using TestMethodAttribute = NUnit.Framework.TestAttribute;
+using AssertFailedException = NUnit.Framework.AssertionException;
+#else
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+#endif
+
+namespace Implab.Test {
+    [TestClass]
+    public class PollingComponentTests {
+        static void ShouldThrow(Action action) {
+            try {
+                action();
+                Assert.Fail();
+            } catch (AssertFailedException) {
+                throw;
+            } catch {
+            }
+        }
+
+        [TestMethod]
+        public void NormalFlowTest() {
+            var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, false);
+
+            Assert.AreEqual(ExecutionState.Created, comp.State);
+
+            comp.Initialize();
+
+            Assert.AreEqual(ExecutionState.Ready, comp.State);
+
+            comp.Start().Join(1000);
+
+            Assert.AreEqual(ExecutionState.Running, comp.State);
+
+            comp.Stop().Join(1000);
+
+            Assert.AreEqual(ExecutionState.Disposed, comp.State);
+
+        }
+
+        [TestMethod]
+        public void ShouldStartTicks() {
+            var signal = new Signal();
+
+            var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true);
+            comp.MockTick = ct => {
+                signal.Set();
+                return Promise.Success;
+            };
+
+            comp.Start().Join(1000);
+            signal.Wait(1000);
+            comp.Stop().Join(1000);
+        }
+
+        [TestMethod]
+        public void StopShouldWaitForTickToComplete() {
+            var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true);
+            var signal = new Signal();
+            var promise = new Promise();
+
+            // timer should tick once
+            comp.MockTick = ct => {
+                signal.Set();
+                return promise;
+            };
+
+            // start timer
+            comp.Start().Join(1000);
+
+            signal.Wait(); // wait for tick
+
+            // try to stop component
+            var stopping = comp.Stop();
+
+            Assert.AreEqual(ExecutionState.Stopping, comp.State);
+            ShouldThrow(() => stopping.Join(100));
+            Assert.AreEqual(ExecutionState.Stopping, comp.State);
+
+            // complete operation
+            promise.Resolve();
+
+            // the component should stop normally
+            stopping.Join(1000);
+
+            Assert.AreEqual(ExecutionState.Disposed, comp.State);
+        }
+
+        [TestMethod]
+        public void ShouldRecoverAfterTickError() {
+            var ticks = 0;
+
+            var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true);
+            var signal = new Signal(); // will signal when timer fires 10 times
+
+            comp.MockTick = ct => {
+                ticks++;
+                if (ticks == 10)
+                    signal.Set();
+                // each time handler dies
+                throw new Exception("tainted handler");
+            };
+
+            comp.Start();
+
+            signal.Wait(1000);
+
+            comp.Stop().Join(1000);
+
+            Assert.AreEqual(ExecutionState.Disposed, comp.State);
+        }
+
+        [TestMethod]
+        public void StopCancelHandlerOnStop() {
+            var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true);
+            var started = new Signal();
+            bool cancelled = false;
+
+            // timer should tick once
+            comp.MockTick = ct => {
+                started.Set();
+
+                while(!ct.IsCancellationRequested) {
+                    Thread.Sleep(1);
+                }
+
+                cancelled = true;
+
+                throw new OperationCanceledException();
+            };
+
+            // start timer
+            comp.Start().Join(1000);
+
+            started.Wait(); // wait for tick
+
+            // try to stop component
+            comp.Stop().Join(1000);
+
+            Assert.AreEqual(true, cancelled);
+
+            Assert.AreEqual(ExecutionState.Disposed, comp.State);
+        }
+
+        [TestMethod]
+        public void FailTickOnStopShouldBeIgnored() {
+            var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true);
+            var started = new Signal();
+            var finish = new Signal();
+
+            // timer should tick once
+            comp.MockTick = ct => {
+                started.Set();
+                finish.Wait();
+                // component is in stopping state here
+                throw new Exception("Die, die, die!!!");
+            };
+
+
+            comp.MockOnError = comp.CallComponentFail;
+
+            // start timer
+            comp.Start().Join(1000);
+
+            started.Wait(); // wait for tick
+
+            // try to stop component
+            var stopping = comp.Stop();
+
+            // the component is in stopping state but it is waiting for the tick handler to complete
+            finish.Set(); // signal the tick handler to finish
+
+            // tick handler should stop rather soon
+            stopping.Join(1000);
+
+            // validate the component is disposed
+            Assert.AreEqual(ExecutionState.Disposed, comp.State);
+        }
+
+        [TestMethod]
+        public void FailTickShouldFailComponent() {
+            var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true);
+            var started = new Signal();
+            var finish = new Signal();
+
+            // timer should tick once
+            comp.MockTick = ct => {
+                started.Set();
+                throw new Exception("Die, die, die!!!");
+            };
+
+
+            comp.MockOnError = err => {
+                comp.CallComponentFail(err);
+                finish.Set();
+            };
+
+            // start timer
+            comp.Start().Join(1000);
+
+            started.Wait(); // wait for tick
+
+            finish.Wait();
+
+            // try to stop component
+            ShouldThrow(() => comp.Stop());
+
+            Assert.AreEqual(ExecutionState.Failed, comp.State);
+            Assert.IsNotNull(comp.LastError);
+            Assert.AreEqual("Die, die, die!!!", comp.LastError.Message);
+
+            comp.Dispose();
+            Assert.AreEqual(ExecutionState.Disposed, comp.State);
+        }
+
+    }
+}
+
--- a/Implab.Test/PromiseHelper.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Test/PromiseHelper.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -10,5 +10,13 @@
                 return retVal;
             });
         }
+
+        public static IPromise Sleep(int timeout) {
+            return AsyncPool.Invoke((ct) => {
+                ct.CancellationRequested(ct.CancelOperation);
+                Thread.Sleep(timeout);
+                return 0;
+            });
+        }
     }
 }
--- a/Implab.Test/RunnableComponentTests.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab.Test/RunnableComponentTests.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -3,6 +3,7 @@
 using System.Threading;
 using Implab.Parallels;
 using Implab.Components;
+using Implab.Test.Mock;
 
 #if MONO
 
@@ -30,46 +31,15 @@
             }
         }
 
-        class Runnable : RunnableComponent {
-            public Runnable(bool initialized) : base(initialized) {
-            }
-                
-            public Action MockInit {
-                get;
-                set;
-            }
 
-            public Func<IPromise> MockStart {
-                get;
-                set;
-            }
-
-            public Func<IPromise> MockStop {
-                get;
-                set;
-            }
-
-            protected override IPromise OnStart() {
-                return MockStart != null ? MockStart() : base.OnStart();
-            }
-
-            protected override IPromise OnStop() {
-                return MockStop != null ? MockStop() : base.OnStart();
-            }
-
-            protected override void OnInitialize() {
-                if (MockInit != null)
-                    MockInit();
-            }
-        }
 
         [TestMethod]
         public void NormalFlowTest() {
-            var comp = new Runnable(false);
+            var comp = new MockRunnableComponent(false);
 
             Assert.AreEqual(ExecutionState.Created, comp.State);
 
-            comp.Init();
+            comp.Initialize();
 
             Assert.AreEqual(ExecutionState.Ready, comp.State);
 
@@ -85,7 +55,7 @@
 
         [TestMethod]
         public void InitFailTest() {
-            var comp = new Runnable(false) {
+            var comp = new MockRunnableComponent(false) {
                 MockInit = () => {
                     throw new Exception("BAD");
                 }
@@ -95,7 +65,7 @@
             ShouldThrow(() => comp.Stop());
             Assert.AreEqual(ExecutionState.Created, comp.State);
 
-            ShouldThrow(comp.Init);
+            ShouldThrow(comp.Initialize);
 
             Assert.AreEqual(ExecutionState.Failed, comp.State);
 
@@ -110,19 +80,82 @@
         [TestMethod]
         public void DisposedTest() {
 
-            var comp = new Runnable(false);
+            var comp = new MockRunnableComponent(false);
             comp.Dispose();
 
             ShouldThrow(() => comp.Start());
             ShouldThrow(() => comp.Stop());
-            ShouldThrow(comp.Init);
+            ShouldThrow(comp.Initialize);
 
             Assert.AreEqual(ExecutionState.Disposed, comp.State);
         }
 
         [TestMethod]
+        public void ShouldCallDisposeOnStop() {
+            var comp = new MockRunnableComponent(true);
+
+            bool disposed = false;
+            comp.MockDispose = (disposing) => {
+                disposed = true;
+            };
+
+            comp.Start().Join(1000);
+            comp.Stop().Join(1000);
+
+            ShouldThrow(() => comp.Start());
+            ShouldThrow(() => comp.Stop());
+            ShouldThrow(comp.Initialize);
+
+            Assert.AreEqual(ExecutionState.Disposed, comp.State);
+            Assert.IsTrue(disposed);
+        }
+
+        [TestMethod]
+        public void ShouldNotCallDisposeOnStop() {
+            var comp = new MockRunnableComponent(true, true);
+
+            bool disposed = false;
+            comp.MockDispose = (disposing) => {
+                disposed = true;
+            };
+
+            comp.Start().Join(1000);
+            comp.Stop().Join(1000);
+
+            Assert.AreEqual(ExecutionState.Ready, comp.State);
+            Assert.IsFalse(disposed);
+        }
+
+        [TestMethod]
+        public void SelfDisposeOnStop() {
+            var comp = new MockRunnableComponent(true, true);
+
+            bool disposed = false;
+            comp.MockDispose = (disposing) => {
+                disposed = true;
+            };
+
+            comp.Start().Join(1000);
+            comp.Stop().Join(1000);
+
+            Assert.AreEqual(ExecutionState.Ready, comp.State);
+            Assert.IsFalse(disposed);
+
+            comp.MockStop = () => {
+                comp.Dispose();
+                return Promise.Success;
+            };
+
+            comp.Start().Join(1000);
+            comp.Stop().Join(1000);
+
+            Assert.AreEqual(ExecutionState.Disposed, comp.State);
+            Assert.IsTrue(disposed);
+        }
+
+        [TestMethod]
         public void StartCancelTest() {
-            var comp = new Runnable(true) {
+            var comp = new MockRunnableComponent(true) {
                 MockStart = () => PromiseHelper.Sleep(100000, 0)
             };
 
@@ -140,7 +173,7 @@
         [TestMethod]
         public void StartStopTest() {
             var stop = new Signal();
-            var comp = new Runnable(true) {
+            var comp = new MockRunnableComponent(true) {
                 MockStart = () => PromiseHelper.Sleep(100000, 0),
                 MockStop = () => AsyncPool.RunThread(stop.Wait)
             };
@@ -160,7 +193,7 @@
 
         [TestMethod]
         public void StartStopFailTest() {
-            var comp = new Runnable(true) {
+            var comp = new MockRunnableComponent(true) {
                 MockStart = () => PromiseHelper.Sleep(100000, 0).Then(null,null,x => { throw new Exception("I'm dead"); })
             };
 
@@ -175,7 +208,7 @@
 
         [TestMethod]
         public void StopCancelTest() {
-            var comp = new Runnable(true) {
+            var comp = new MockRunnableComponent(true) {
                 MockStop = () => PromiseHelper.Sleep(100000, 0)
             };
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab.Test/packages.config	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="utf-8"?>
+<packages>
+  <package id="NUnit" version="2.6.4" targetFramework="net45" />
+</packages>
\ No newline at end of file
--- a/Implab/Automaton/AutomatonTransition.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Automaton/AutomatonTransition.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -28,6 +28,14 @@
         public override int GetHashCode() {
             return s1 + s2 + edge;
         }
+
+        public static bool operator == (AutomatonTransition rv, AutomatonTransition lv) {
+            return rv.Equals(lv);
+        }
+
+        public static bool operator !=(AutomatonTransition rv, AutomatonTransition lv) {
+            return rv.Equals(lv);
+        }
     }
 }
 
--- a/Implab/Components/ComponentContainer.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Components/ComponentContainer.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -8,7 +8,8 @@
     /// </summary>
     /// <remarks>Instanses of this class are thread safe.</remarks>
     public class ComponentContainer<T> : Disposable, ICollection<T> {
-        readonly HashSet<T> m_components = new HashSet<T>();
+        List<T> m_components = new List<T>();
+        readonly object m_lock = new object();
 
         /// <summary>
         /// Removes currently stored compoenents from the container and disposes them if possible.
@@ -17,12 +18,11 @@
         /// A new components may be added before this method completes.
         /// </remarks>
         public void Clear() {
-            T[] removed;
+            List<T> removed;
 
-            lock (m_components) {
-                removed = new T[m_components.Count];
-                m_components.CopyTo(removed);
-                m_components.Clear();
+            lock (m_lock) {
+                removed = m_components;
+                m_components = new List<T>();
             }
 
             foreach (var item in removed.OfType<IDisposable>())
@@ -34,7 +34,7 @@
         /// </summary>
         /// <param name="item">The item to check.</param>
         public bool Contains(T item) {
-            lock (m_components)
+            lock (m_lock)
                 return m_components.Contains(item);
         }
 
@@ -44,7 +44,7 @@
         /// <param name="array">A destination array for components.</param>
         /// <param name="arrayIndex">A starting index in the destination array.</param>
         public void CopyTo(T[] array, int arrayIndex) {
-            lock (m_components)
+            lock (m_lock)
                 m_components.CopyTo(array, arrayIndex);
         }
 
@@ -53,7 +53,7 @@
         /// </summary>
         /// <param name="item">The item to remove.</param>
         public bool Remove(T item) {
-            lock (m_components)
+            lock (m_lock)
                 return m_components.Remove(item);
         }
 
@@ -62,7 +62,7 @@
         /// </summary>
         public int Count {
             get {
-                lock (m_components)
+                lock (m_lock)
                     return m_components.Count;
             }
         }
@@ -84,12 +84,11 @@
         /// </summary>
         /// <returns>The enumerator.</returns>
         public IEnumerator<T> GetEnumerator() {
-            T[] items;
-            lock (m_components) {
-                items = new T[m_components.Count];
+            T[] items = new T[m_components.Count];
+            lock (m_lock) {
                 m_components.CopyTo(items);
-                return (IEnumerator<T>)items.GetEnumerator();
             }
+            return (IEnumerator<T>)items.GetEnumerator();
         }
 
         System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
@@ -105,13 +104,15 @@
         /// </remarks>
         public void Add(T item) {
             Safe.ArgumentNotNull(item, "item");
-
-            lock (m_components) {
+            bool dispose = false;
+            lock (m_lock) {
                 if (IsDisposed)
-                    Safe.Dispose(item);
+                    dispose = true;
                 else
                     m_components.Add(item);
             }
+            if (dispose)
+                Safe.Dispose(item);
         }
 
         /// <summary>
@@ -119,8 +120,10 @@
         /// </summary>
         /// <param name="disposing">If set to <c>true</c> the collection is disposing.</param>
         protected override void Dispose(bool disposing) {
+            if (disposing)
+                Clear();
+
             base.Dispose(disposing);
-            Clear();
         }
     }
 }
--- a/Implab/Components/Disposable.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Components/Disposable.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -1,5 +1,6 @@
 using Implab.Diagnostics;
 using System;
+using System.Diagnostics.CodeAnalysis;
 using System.Threading;
 
 namespace Implab.Components {
@@ -7,7 +8,7 @@
     /// Base class the objects which support disposing.
     /// </summary>
     public class Disposable : IDisposable {
-        
+
         int m_disposed;
 
         public event EventHandler Disposed;
@@ -74,13 +75,12 @@
         /// из нескольких потоков.
         /// </remarks>
         protected virtual void Dispose(bool disposing) {
-            if (disposing) {
-                EventHandler temp = Disposed;
-                if (temp != null)
-                    temp(this, EventArgs.Empty);
-            }
+            if (disposing)
+                Disposed.DispatchEvent(this, EventArgs.Empty);
+
         }
 
+        [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Dipose(bool) and GC.SuppessFinalize are called")]
         public void Dispose() {
             if (Interlocked.Increment(ref m_disposed) == 1) {
                 Dispose(true);
--- a/Implab/Components/ExecutionState.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Components/ExecutionState.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -13,6 +13,12 @@
 
         Running,
 
+        Suspending,
+
+        Suspended,
+
+        Resuming,
+
         Stopping,
 
         Failed,
--- a/Implab/Components/IInitializable.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Components/IInitializable.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -3,8 +3,8 @@
 namespace Implab.Components {
     /// <summary>
     /// Initializable components are created and initialized in two steps, first we have create the component,
-    /// then we have to complete it's creation by calling an <see cref="Init()"/> method. All parameters needed
-    /// to complete the initialization must be passed before the calling <see cref="Init()"/>
+    /// then we have to complete it's creation by calling an <see cref="Initialize()"/> method. All parameters needed
+    /// to complete the initialization must be passed before the calling <see cref="Initialize()"/>
     /// </summary>
     public interface IInitializable {
         /// <summary>
@@ -12,10 +12,10 @@
         /// </summary>
         /// <remarks>
         /// Normally virtual methods shouldn't be called from the constructor, due to the incomplete object state, but
-        /// they can be called from this method. This method is also usefull when we constructing a complex grpah
+        /// they can be called from this method. This method is aьуерщlso usefull when we constructing a complex grpah
         /// of components where cyclic references may take place.
         /// </remarks>
-        void Init();
+        void Initialize();
     }
 }
 
--- a/Implab/Components/IRunnable.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Components/IRunnable.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -1,13 +1,29 @@
 using System;
 
 namespace Implab.Components {
-    public interface IRunnable {
+    /// <summary>
+    /// Interface for the component which performs a long running task.
+    /// </summary>
+    /// <remarks>
+    /// <para>The component also should implement <see cref="IDisposable"/> interface to be able to release used resources.</para>
+    /// <para>All methods of this interface must be a thread safe. If the operation is not applicable in the current state the
+    /// method should throw an exception and keep the current state unchanged.</para>
+    /// </remarks>
+    public interface IRunnable : IDisposable {
+        /// <summary>
+        /// Starts this instance
+        /// </summary>
         IPromise Start();
 
+        /// <summary>
+        /// Stops this instance, after the instance is stopped it can move to Failed, Ready or Disposed state, in case with the last it can't be reused.
+        /// </summary>
         IPromise Stop();
 
         ExecutionState State { get; }
 
+        event EventHandler<StateChangeEventArgs> StateChanged;
+
         Exception LastError { get; }
     }
 }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/Components/PollingComponent.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,155 @@
+using System;
+using System.Threading;
+using Implab.Diagnostics;
+
+namespace Implab.Components {
+    public class PollingComponent : RunnableComponent {
+        readonly Timer m_timer;
+        readonly Func<Func<ICancellationToken, IPromise>, IPromise> m_dispatcher;
+        readonly TimeSpan m_interval;
+
+        readonly object m_lock = new object();
+
+        ActionTask m_pending;
+
+        protected PollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, IPromise> dispatcher, bool initialized) : base(initialized) {
+            m_timer = new Timer(OnInternalTick);
+
+            m_interval = interval;
+            m_dispatcher = dispatcher;
+        }
+
+        protected override IPromise OnStart() {
+            m_timer.Change(TimeSpan.Zero, m_interval);
+
+            return base.OnStart();
+        }
+
+        void OnInternalTick(object state) {
+            if (StartTick()) {
+                try {
+                    if (m_dispatcher != null) {
+                        var result = m_dispatcher(OnTick);
+                        m_pending.CancellationRequested(result.Cancel);
+                        AwaitTick(result);
+                    } else {
+                        AwaitTick(OnTick(m_pending));
+                    }
+                } catch (Exception error) {
+                    HandleTickError(error);
+                }
+            }   
+        }
+
+        /// <summary>
+        /// Checks wheather there is no running handler in the component and marks that the handler is starting.
+        /// </summary>
+        /// <returns>boolean value, true - the new tick handler may be invoked, false - a tick handler is already running or a component isn't running.</returns>
+        /// <remarks>
+        /// If the component is stopping no new handlers can be run. Every successful call to this method must be completed with either AwaitTick or HandleTickError handlers.
+        /// </remarks>
+        protected virtual bool StartTick() {
+            lock (m_lock) {
+                if (State != ExecutionState.Running || m_pending != null)
+                    return false;
+                // actually the component may be in a not running state here (stopping, disposed or what ever),
+                // but OnStop method also locks on the same object and will handle operation in m_pending
+                m_pending = new ActionTask(
+                    () => {
+                        // only one operation is running, it's safe to assing m_pending from it
+                        m_pending = null;
+                    },
+                    ex => {
+                        try {
+                            OnTickError(ex);
+                            // Analysis disable once EmptyGeneralCatchClause
+                        } catch {
+                        } finally {
+                            m_pending = null;
+                        }
+                        // suppress error
+                    },
+                    ex => {
+                        try {
+                            OnTickCancel(ex);
+                            // Analysis disable once EmptyGeneralCatchClause
+                        } catch {
+                        } finally {
+                            m_pending = null;
+                        }
+                        // supress cancellation
+                    },
+                    false
+                );
+                return true;
+            }
+        }
+
+        /// <summary>
+        /// Awaits the tick.
+        /// </summary>
+        /// <param name="tick">Tick.</param>
+        /// <remarks>
+        /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled.
+        /// </remarks>
+        void AwaitTick(IPromise tick) {
+            if (tick == null) {
+                m_pending.Resolve();
+            } else {
+                tick.On(
+                    m_pending.Resolve,
+                    m_pending.Reject,
+                    m_pending.CancelOperation
+                );
+            }
+        }
+
+        /// <summary>
+        /// Handles the tick error.
+        /// </summary>
+        /// <remarks>
+        /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled.
+        /// </remarks>
+        void HandleTickError(Exception error) {
+            m_pending.Reject(error);
+        }
+
+        protected virtual void OnTickError(Exception error) {
+        }
+
+        protected virtual void OnTickCancel(Exception error) {
+        }
+
+        /// <summary>
+        /// Invoked when the timer ticks, use this method to implement your logic
+        /// </summary>
+        protected virtual IPromise OnTick(ICancellationToken cancellationToken) {
+            return Promise.Success;
+        }
+
+        protected override IPromise OnStop() {
+            m_timer.Change(-1, -1);
+
+            // the component is in the stopping state
+            lock (m_lock) {
+                // after this lock no more pending operations could be created
+                var pending = m_pending;
+                // m_pending could be fulfilled and set to null already
+                if (pending != null) {
+                    pending.Cancel();
+                    return pending.Then(base.OnStop);
+                }
+            }
+
+            return base.OnStop();
+        }
+
+        protected override void Dispose(bool disposing) {
+            if (disposing)
+                m_timer.Dispose();
+            
+            base.Dispose(disposing);
+        }
+    }
+}
+
--- a/Implab/Components/RunnableComponent.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Components/RunnableComponent.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -1,5 +1,6 @@
 using System;
-
+using System.Diagnostics.CodeAnalysis;
+
 namespace Implab.Components {
     public abstract class RunnableComponent : IDisposable, IRunnable, IInitializable {
         enum Commands {
@@ -9,54 +10,95 @@
             Start,
             Stop,
             Dispose,
-            Last = Dispose
+            Reset,
+            Last = Reset
         }
 
         class StateMachine {
-            static readonly ExecutionState[,] _transitions;
+            public static readonly ExecutionState[,] ReusableTransitions;
+            public static readonly ExecutionState[,] NonreusableTransitions;
+
+            class StateBuilder {
+                readonly ExecutionState[,] m_states;
+
+                public ExecutionState[,] States {
+                    get { return m_states; }
+                }
+                public StateBuilder(ExecutionState[,] states) {
+                    m_states = states;
+                }
+
+                public StateBuilder() {
+                    m_states = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1];
+                }
+
+                public StateBuilder Edge(ExecutionState s1, ExecutionState s2, Commands cmd) {
+                    m_states[(int)s1, (int)cmd] = s2;
+                    return this;
+                }
+
+                public StateBuilder Clone() {
+                    return new StateBuilder((ExecutionState[,])m_states.Clone());
+                }
+            }
 
             static StateMachine() {
-                _transitions = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1];
+                ReusableTransitions = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1];
 
-                Edge(ExecutionState.Created, ExecutionState.Initializing, Commands.Init);
-                Edge(ExecutionState.Created, ExecutionState.Disposed, Commands.Dispose);
+                var common = new StateBuilder()
+                    .Edge(ExecutionState.Created, ExecutionState.Initializing, Commands.Init)
+                    .Edge(ExecutionState.Created, ExecutionState.Disposed, Commands.Dispose)
 
-                Edge(ExecutionState.Initializing, ExecutionState.Ready, Commands.Ok);
-                Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail);
+                    .Edge(ExecutionState.Initializing, ExecutionState.Ready, Commands.Ok)
+                    .Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail)
 
-                Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start);
-                Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose);
+                    .Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start)
+                    .Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose)
+
+                    .Edge(ExecutionState.Starting, ExecutionState.Running, Commands.Ok)
+                    .Edge(ExecutionState.Starting, ExecutionState.Failed, Commands.Fail)
+                    .Edge(ExecutionState.Starting, ExecutionState.Stopping, Commands.Stop)
+                    .Edge(ExecutionState.Starting, ExecutionState.Disposed, Commands.Dispose)
 
-                Edge(ExecutionState.Starting, ExecutionState.Running, Commands.Ok);
-                Edge(ExecutionState.Starting, ExecutionState.Failed, Commands.Fail);
-                Edge(ExecutionState.Starting, ExecutionState.Stopping, Commands.Stop);
-                Edge(ExecutionState.Starting, ExecutionState.Disposed, Commands.Dispose);
+                    .Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail)
+                    .Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop)
+                    .Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose)
+
+                    .Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose)
+                    .Edge(ExecutionState.Failed, ExecutionState.Initializing, Commands.Reset)
+
+                    .Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail)
+                    .Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose)
+
+                    .Edge(ExecutionState.Disposed, ExecutionState.Disposed, Commands.Dispose);
 
-                Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail);
-                Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop);
-                Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose);
+                var reusable = common
+                    .Clone()
+                    .Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok);
+
+                var nonreusable = common
+                    .Clone()
+                    .Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok);
+                
+                NonreusableTransitions = nonreusable.States;
+                ReusableTransitions = reusable.States;
 
-                Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail);
-                Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok);
-
-                Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose);
             }
 
-            static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) {
-                _transitions[(int)s1, (int)cmd] = s2;
-            }
+            readonly ExecutionState[,] m_states;
 
             public ExecutionState State {
                 get;
                 private set;
             }
 
-            public StateMachine(ExecutionState initial) {
+            public StateMachine(ExecutionState[,] states, ExecutionState initial) {
                 State = initial;
+                m_states = states;
             }
 
             public bool Move(Commands cmd) {
-                var next = _transitions[(int)State, (int)cmd];
+                var next = m_states[(int)State, (int)cmd];
                 if (next == ExecutionState.Undefined)
                     return false;
                 State = next;
@@ -67,46 +109,167 @@
         IPromise m_pending;
         Exception m_lastError;
 
-        readonly StateMachine m_stateMachine;
-
-        protected RunnableComponent(bool initialized) {
-            m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created);
-        }
-
-        protected virtual int DisposeTimeout {
-            get {
-                return 10000;
-            }
+        readonly StateMachine m_stateMachine;
+        readonly bool m_reusable;
+        public event EventHandler<StateChangeEventArgs> StateChanged;
+
+        /// <summary>
+        /// Initializes component state. 
+        /// </summary>
+        /// <param name="initialized">If set, the component initial state is <see cref="ExecutionState.Ready"/> and the component is ready to start, otherwise initialization is required.</param>
+        /// <param name="reusable">If set, the component may start after it has been stopped, otherwise the component is disposed after being stopped.</param>
+        protected RunnableComponent(bool initialized, bool reusable) {
+            m_stateMachine = new StateMachine(
+                reusable ? StateMachine.ReusableTransitions : StateMachine.NonreusableTransitions,
+                initialized ? ExecutionState.Ready : ExecutionState.Created
+            );
+            m_reusable = reusable;
+        }
+
+        /// <summary>
+        /// Initializes component state. The component created with this constructor is not reusable, i.e. it will be disposed after stop.
+        /// </summary>
+        /// <param name="initialized">If set, the component initial state is <see cref="ExecutionState.Ready"/> and the component is ready to start, otherwise initialization is required.</param>
+        protected RunnableComponent(bool initialized) : this(initialized, false) {
         }
 
         void ThrowInvalidCommand(Commands cmd) {
             if (m_stateMachine.State == ExecutionState.Disposed)
-                throw new ObjectDisposedException(ToString());
-            
-            throw new InvalidOperationException(String.Format("Commnd {0} is not allowed in the state {1}", cmd, m_stateMachine.State));
+                throw new ObjectDisposedException(ToString());
+
+            throw new InvalidOperationException(String.Format("Command {0} is not allowed in the state {1}", cmd, m_stateMachine.State));
+        }
+
+        bool MoveIfInState(Commands cmd, IPromise pending, Exception error, ExecutionState state) {
+            ExecutionState prev, current;
+            lock (m_stateMachine) {
+                if (m_stateMachine.State != state)
+                    return false;
+
+                prev = m_stateMachine.State;
+                if (!m_stateMachine.Move(cmd))
+                    ThrowInvalidCommand(cmd);
+                current = m_stateMachine.State;
+
+                m_pending = pending;
+                m_lastError = error;
+            }
+            if (prev != current)
+                OnStateChanged(prev, current, error);
+            return true;
         }
 
-        void Move(Commands cmd) {
-            if (!m_stateMachine.Move(cmd))
-                ThrowInvalidCommand(cmd);
+        bool MoveIfPending(Commands cmd, IPromise pending, Exception error, IPromise expected) {
+            ExecutionState prev, current;
+            lock (m_stateMachine) {
+                if (m_pending != expected)
+                    return false;
+                prev = m_stateMachine.State;
+                if (!m_stateMachine.Move(cmd))
+                    ThrowInvalidCommand(cmd);
+                current = m_stateMachine.State;
+                m_pending = pending;
+                m_lastError = error;
+            }
+            if (prev != current)
+                OnStateChanged(prev, current, error);
+            return true;
+        }
+
+        IPromise Move(Commands cmd, IPromise pending, Exception error) {
+            ExecutionState prev, current;
+            IPromise ret;
+            lock (m_stateMachine) {
+                prev = m_stateMachine.State;
+                if (!m_stateMachine.Move(cmd))
+                    ThrowInvalidCommand(cmd);
+                current = m_stateMachine.State;
+
+                ret = m_pending;
+                m_pending = pending;
+                m_lastError = error;
+
+            }
+            if (prev != current)
+                OnStateChanged(prev, current, error);
+            return ret;
         }
 
-        void Invoke(Commands cmd, Action action) {
-            lock (m_stateMachine) 
-                Move(cmd);
-            
+        /// <summary>
+        /// Handles the state of the component change event, raises the <see cref="StateChanged"/>  event, handles
+        /// the transition to the <see cref="ExecutionState.Disposed"/> state (calls <see cref="Dispose(bool)"/> method).
+        /// </summary>
+        /// <param name="previous">The previous state</param>
+        /// <param name="current">The current state</param>
+        /// <param name="error">The last error if any.</param>
+        /// <remarks>
+        /// <para>
+        /// If the previous state and the current state are same this method isn't called, such situiation is treated
+        /// as the component hasn't changed it's state.
+        /// </para>
+        /// <para>
+        /// When overriding this method ensure the call is made to the base implementation, otherwise it will lead to 
+        /// the wrong behavior of the component.
+        /// </para>
+        /// </remarks>
+        protected virtual void OnStateChanged(ExecutionState previous, ExecutionState current, Exception error) {
+            StateChanged.DispatchEvent(
+                this,
+                new StateChangeEventArgs {
+                    State = current,
+                    LastError = error
+                }
+            );
+            if (current == ExecutionState.Disposed) {
+                GC.SuppressFinalize(this);
+                Dispose(true);
+            }
+        }
+
+        /// <summary>
+        /// Moves the component from running to failed state.
+        /// </summary>
+        /// <param name="error">The exception which is describing the error.</param>
+        protected bool Fail(Exception error) {
+            return MoveIfInState(Commands.Fail, null, error, ExecutionState.Running);
+        }
+
+        /// <summary>
+        /// Tries to reset <see cref="ExecutionState.Failed"/> state to <see cref="ExecutionState.Ready"/>.
+        /// </summary>
+        /// <returns>True if component is reset to <see cref="ExecutionState.Ready"/>, false if the componet wasn't
+        /// in <see cref="ExecutionState.Failed"/> state.</returns>
+        /// <remarks>
+        /// This method checks the current state of the component and if it's in <see cref="ExecutionState.Failed"/>
+        /// moves component to <see cref="ExecutionState.Initializing"/>.
+        /// The <see cref="OnResetState()"/> is called and if this method completes succesfully the component moved
+        /// to <see cref="ExecutionState.Ready"/> state, otherwise the component is moved to <see cref="ExecutionState.Failed"/>
+        /// state. If <see cref="OnResetState()"/> throws an exception it will be propagated by this method to the caller.
+        /// </remarks>
+        protected bool ResetState() {
+            if (!MoveIfInState(Commands.Reset, null, null, ExecutionState.Failed))
+                return false;
+
             try {
-                action();
-                lock(m_stateMachine)
-                    Move(Commands.Ok);
-
+                OnResetState();
+                Move(Commands.Ok, null, null);
+                return true;
             } catch (Exception err) {
-                lock (m_stateMachine) {
-                    Move(Commands.Fail);
-                    m_lastError = err;
-                }
+                Move(Commands.Fail, null, err);
                 throw;
-            }
+            }
+        }
+
+        /// <summary>
+        /// This method is called by <see cref="ResetState"/> to reinitialize component in the failed state.
+        /// </summary>
+        /// <remarks>
+        /// Default implementation throws <see cref="NotImplementedException"/> which will cause the component
+        /// fail to reset it's state and it left in <see cref="ExecutionState.Failed"/> state.
+        /// If this method doesn't throw exceptions the component is moved to <see cref="ExecutionState.Ready"/> state.
+        /// </remarks>
+        protected virtual void OnResetState() {
+            throw new NotImplementedException();
         }
 
         IPromise InvokeAsync(Commands cmd, Func<IPromise> action, Action<IPromise, IDeferred> chain) {
@@ -115,40 +278,20 @@
 
             var task = new ActionChainTask(action, null, null, true);
 
-            lock (m_stateMachine) {
-                Move(cmd);
-            
-                prev = m_pending;
+            Action<Exception> errorOrCancel = e => {
+                if (e == null)
+                    e = new OperationCanceledException();
+                MoveIfPending(Commands.Fail, null, e, promise);
+                throw new PromiseTransientException(e);
+            };
 
-                Action<Exception> errorOrCancel = e => {
-                    if (e == null)
-                        e = new OperationCanceledException();
-                    
-                    lock (m_stateMachine) {
-                        if (m_pending == promise) {
-                            Move(Commands.Fail);
-                            m_pending = null;
-                            m_lastError = e;
-                        }
-                    }
-                    throw new PromiseTransientException(e);
-                };
+            promise = task.Then(
+                () => MoveIfPending(Commands.Ok, null, null, promise),
+                errorOrCancel,
+                errorOrCancel
+            );
 
-                promise = task.Then(
-                    () => {
-                        lock(m_stateMachine) {
-                            if (m_pending == promise) {
-                                Move(Commands.Ok);
-                                m_pending = null;
-                            }
-                        }
-                    },
-                    errorOrCancel,
-                    errorOrCancel 
-                );
-
-                m_pending = promise;
-            }
+            prev = Move(cmd, promise, null);
 
             if (prev == null)
                 task.Resolve();
@@ -161,8 +304,16 @@
 
         #region IInitializable implementation
 
-        public void Init() {
-            Invoke(Commands.Init, OnInitialize);
+        public void Initialize() {
+            Move(Commands.Init, null, null);
+
+            try {
+                OnInitialize();
+                Move(Commands.Ok, null, null);
+            } catch (Exception err) {
+                Move(Commands.Fail, null, err);
+                throw;
+            }
         }
 
         protected virtual void OnInitialize() {
@@ -177,15 +328,15 @@
         }
 
         protected virtual IPromise OnStart() {
-            return Promise.SUCCESS;
+            return Promise.Success;
         }
 
         public IPromise Stop() {
-            return InvokeAsync(Commands.Stop, OnStop, StopPending).Then(Dispose);
+            return InvokeAsync(Commands.Stop, OnStop, StopPending);
         }
 
         protected virtual IPromise OnStop() {
-            return Promise.SUCCESS;
+            return Promise.Success;
         }
 
         /// <summary>
@@ -224,39 +375,35 @@
 
         #region IDisposable implementation
 
+        /// <summary>
+        /// Releases all resource used by the <see cref="Implab.Components.RunnableComponent"/> object.
+        /// </summary>
+        /// <remarks>
+        /// <para>Will not try to stop the component, it will just release all resources.
+        /// To cleanup the component gracefully use <see cref="Stop()"/> method.</para>
+        /// <para>
+        /// In normal cases the <see cref="Dispose()"/> method shouldn't be called, the call to the <see cref="Stop()"/>
+        /// method is sufficient to cleanup the component. Call <see cref="Dispose()"/> only to cleanup after errors,
+        /// especially if <see cref="Stop"/> method is failed. Using this method insted of <see cref="Stop()"/> may
+        /// lead to the data loss by the component.
+        /// </para></remarks>
+        [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Dipose(bool) and GC.SuppessFinalize are called")]
         public void Dispose() {
-            IPromise pending;
-            lock (m_stateMachine) {
-                if (m_stateMachine.State == ExecutionState.Disposed)
-                    return;
-
-                Move(Commands.Dispose);
-
-                GC.SuppressFinalize(this);
-
-                pending = m_pending;
-                m_pending = null;
-            }
-            if (pending != null) {
-                pending.Cancel();
-                pending.Timeout(DisposeTimeout).On(
-                    () => Dispose(true, null),
-                    err => Dispose(true, err),
-                    reason => Dispose(true, new OperationCanceledException("The operation is cancelled", reason))
-                );
-            } else {
-                Dispose(true, m_lastError);
-            }
+            Move(Commands.Dispose, null, null);
         }
 
         ~RunnableComponent() {
-            Dispose(false, null);
+            Dispose(false);
         }
 
         #endregion
 
-        protected virtual void Dispose(bool disposing, Exception lastError) {
-
+        /// <summary>
+        /// Releases all resources used by the component, called automatically, override this method to implement your cleanup.
+        /// </summary>
+        /// <param name="disposing">true if this method is called during normal dispose process.</param>
+        /// <param name="pending">The operation which is currenty pending</param>
+        protected virtual void Dispose(bool disposing) {
         }
 
     }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/Components/StateChangeEventArgs.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,16 @@
+using System;
+
+namespace Implab.Components
+{
+    public class StateChangeEventArgs : EventArgs {
+        /// <summary>
+        /// The error information if any
+        /// </summary>
+        public Exception LastError { get; set; }
+
+        /// <summary>
+        /// The state of the service corresponding to this event
+        /// </summary>
+        public ExecutionState State { get; set; }
+    }
+}
--- a/Implab/Diagnostics/EventText.cs	Tue Aug 22 09:35:54 2017 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,12 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Linq;
-using System.Text;
-
-namespace Implab.Diagnostics {
-    public struct EventText {
-        public int indent;
-
-        public string content;
-    }
-}
--- a/Implab/Diagnostics/ListenerBase.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Diagnostics/ListenerBase.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -59,8 +59,8 @@
 
         public void UnsubscribeAll() {
             lock (m_subscriptions) {
-                foreach (var subscription in m_subscriptions.Values)
-                    subscription();
+                foreach (var remove in m_subscriptions.Values)
+                    remove();
                 m_subscriptions.Clear();
             }
         }
--- a/Implab/Diagnostics/LogChannel.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Diagnostics/LogChannel.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -69,7 +69,7 @@
                     this,
                     new LogEventArgs<TEvent>(
                         data,
-                        Name,
+                        this,
                         traceContext.ThreadId,
                         traceContext.CurrentOperation,
                         traceContext.CurrentOperation.Duration
@@ -77,5 +77,9 @@
                 );
             }
         }
+
+        public override string ToString() {
+            return Name;
+        }
     }
 }
--- a/Implab/Diagnostics/LogEventArgs.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Diagnostics/LogEventArgs.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -2,7 +2,7 @@
 
 namespace Implab.Diagnostics {
     public class LogEventArgs : EventArgs {
-        public string ChannelName {
+        public object Channel {
             get;
             private set;
         }
@@ -18,8 +18,8 @@
             get;
             private set;
         }
-        public LogEventArgs(string channelName, int threadId, LogicalOperation operation, int timeOffset) {
-            ChannelName = channelName;
+        public LogEventArgs(object channel, int threadId, LogicalOperation operation, int timeOffset) {
+            Channel = channel;
             ThreadId = threadId;
             Operation = operation;
             OperationTimeOffset = timeOffset;
--- a/Implab/Diagnostics/LogEventArgsT.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Diagnostics/LogEventArgsT.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -5,7 +5,7 @@
             private set;
         }
 
-        public LogEventArgs(TEvent value,string channelName, int threadId, LogicalOperation operation, int timeOffset) : base(channelName, threadId, operation, timeOffset) {
+        public LogEventArgs(TEvent value,object channel, int threadId, LogicalOperation operation, int timeOffset) : base(channel, threadId, operation, timeOffset) {
             Value = value;
         }
     }
--- a/Implab/Diagnostics/OperationContext.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Diagnostics/OperationContext.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -30,11 +30,11 @@
                 else if (m_ownership)
                     m_current = LogicalOperation.EMPTY;
                 else {
-                    TraceLog.TraceWarning("DetachLogicalOperation can't be applied in the current context");
+                    TraceLog.TraceWarning("DetachLogicalOperation can't be performed in the current context");
                     detached = LogicalOperation.EMPTY;
                 }
             } else {
-                TraceLog.TraceWarning("DetachLogicalOperation can't be applied in the current context");
+                TraceLog.TraceWarning("DetachLogicalOperation can't be performed in the current context");
             }
 
             return detached;
@@ -50,7 +50,7 @@
                     m_ownership = false;
                 }
             } else {
-                TraceLog.TraceWarning("EndLogicalOperation can't be applied in the current context");
+                TraceLog.TraceWarning("EndLogicalOperation can't be performed in the current context");
             }
             return current;
         }
--- a/Implab/Diagnostics/TextFileListener.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Diagnostics/TextFileListener.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -5,6 +5,7 @@
 namespace Implab.Diagnostics {
     public class TextFileListener: ListenerBase {
         readonly TextWriter m_textWriter;
+        readonly object m_lock = new object();
 
         public TextFileListener(string fileName) {
             m_textWriter = File.CreateText(fileName);
@@ -18,9 +19,9 @@
             var msg = new StringBuilder();
             for (int i = 0; i < args.Operation.Level; i++)
                 msg.Append("  ");
-            msg.AppendFormat("[{0}]:{1}: {2}", args.ThreadId, args.ChannelName, entry);
+            msg.AppendFormat("[{0}]+{3}ms:{1}: {2}", args.ThreadId, args.Channel, entry, args.OperationTimeOffset);
 
-            lock (m_textWriter) {
+            lock (m_lock) {
                 if (!IsDisposed) {
                     // тут гарантировано еще не освобожден m_textWriter
                     m_textWriter.WriteLine(msg);
@@ -35,7 +36,7 @@
             base.Dispose(disposing);
             if (disposing) {
                 // IsDisposed = true
-                lock (m_textWriter) {
+                lock (m_lock) {
                     Safe.Dispose(m_textWriter);
                 }
             }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/Diagnostics/Trace.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,75 @@
+using System;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace Implab.Diagnostics {
+    public static class Trace<T> {
+
+        readonly static LogChannel<TraceEvent> _channel = new LogChannel<TraceEvent>(typeof(T).Name);
+
+        public static LogChannel<TraceEvent> Channel {
+            get { return _channel; }
+        }
+        
+        /// <summary>
+        /// Starts the logical operation nested to the current operation nested to the current one.
+        /// </summary>
+        [Conditional("TRACE")]
+        public static void StartLogicalOperation() {
+            TraceContext.Instance.StartLogicalOperation();
+
+        }
+
+        /// <summary>
+        /// Starts the logical operation with the specified name, this name is usefull in logs.
+        /// </summary>
+        /// <param name="name">Name.</param>
+        [Conditional("TRACE")]
+        public static void StartLogicalOperation(string name) {
+            Channel.LogEvent(new TraceEvent(TraceContext.Instance.CurrentOperation, TraceEventType.OperationStarted, name));
+            TraceContext.Instance.StartLogicalOperation(name);
+        }
+
+        /// <summary>
+        /// Ends the logical operation and restores the previous one.
+        /// </summary>
+        [Conditional("TRACE")]
+        public static void EndLogicalOperation() {
+            var op = TraceContext.Instance.EndLogicalOperation();
+            Channel.LogEvent(new TraceEvent(op, TraceEventType.OperationCompleted, String.Format("-{0} : {1}ms", op.Name, op.Duration)));
+        }
+
+        /// <summary>
+        /// Writes an informational message.
+        /// </summary>
+        /// <param name="format">Format.</param>
+        /// <param name="arguments">Arguments.</param>
+        [Conditional("TRACE")]
+        public static void Log(string format, params object[] arguments) {
+            Channel.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Information, format, arguments));
+        }
+
+        /// <summary>
+        /// Writes a warning message.
+        /// </summary>
+        /// <param name="format">Format.</param>
+        /// <param name="arguments">Arguments.</param>
+        [Conditional("TRACE")]
+        public static void Warn(string format, params object[] arguments) {
+            Channel.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Warning, format, arguments));
+        }
+
+        [Conditional("TRACE")]
+        public static void Error(string format, params object[] arguments) {
+            Channel.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Error, format, arguments));
+        }
+
+        [Conditional("TRACE")]
+        public static void Error(Exception err) {
+            Error("{0}", err);
+        }
+    }
+}
--- a/Implab/Diagnostics/TraceContext.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Diagnostics/TraceContext.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -48,7 +48,7 @@
         }
 
         public void StartLogicalOperation(string name) {
-            LogChannel<TraceEvent>.Default.LogEvent(new TraceEvent(TraceEventType.OperationStarted, name));
+            //LogChannel<TraceEvent>.Default.LogEvent(new TraceEvent(TraceContext.Instance.CurrentOperation, TraceEventType.OperationStarted, name));
             m_current.BeginLogicalOperation(name);
         }
 
@@ -56,9 +56,8 @@
             StartLogicalOperation(String.Empty);
         }
 
-        public void EndLogicalOperation() {
-            var op = m_current.EndLogicalOperation();
-            LogChannel<TraceEvent>.Default.LogEvent(new TraceEvent(TraceEventType.OperationCompleted, String.Format("-{0} : {1}ms",op.Name, op.Duration)));
+        public LogicalOperation EndLogicalOperation() {
+            return m_current.EndLogicalOperation();
         }
 
         public LogicalOperation DetachLogicalOperation() {
--- a/Implab/Diagnostics/TraceEvent.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Diagnostics/TraceEvent.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -12,18 +12,36 @@
             private set;
         }
 
-        public TraceEvent(TraceEventType type, string message) {
+        /// <summary>
+        /// The logical operation this event belongs to.
+        /// </summary>
+        public LogicalOperation Operation {
+            get;
+            private set;
+        }
+
+        /// <summary>
+        /// Gets the time offset in milliseconds from the start of the operation, if the operation is not specified the value is zero.
+        /// </summary>
+        public int OperationTime {
+            get;
+            private set;
+        }
+
+        public TraceEvent(LogicalOperation operation, TraceEventType type, string message) {
             EventType = type;
             Message = message;
+            Operation = operation;
+            if (operation != null)
+                OperationTime = operation.Duration;
         }
 
         public override string ToString() {
-            /*return EventType == TraceEventType.Information ? Message : String.Format("{0}: {1}", EventType, Message);*/
             return Message;
         }
 
-        public static TraceEvent Create(TraceEventType type, string format, params object[] args) {
-            return new TraceEvent(type, format == null ? String.Empty : String.Format(format, args));
+        public static TraceEvent Create(LogicalOperation operation, TraceEventType type, string format, params object[] args) {
+            return new TraceEvent(operation, type, format == null ? String.Empty : args == null || args.Length == 0 ? format : String.Format(format, args));
         }
     }
 }
--- a/Implab/Diagnostics/TraceLog.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Diagnostics/TraceLog.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -7,39 +7,59 @@
 
 namespace Implab.Diagnostics {
     /// <summary>
-    /// Класс для публикации событий выполнения программы, события публикуются через <see cref="LogChannel{TraceEvent}"/>.
-    /// Журнал трассировки отражает логический ход выполнения программы и существует всегда, поскольку тесно связан с 
-    /// контекстом трассировки.
+    /// This class is used to trace a logical flow of the application, it publishes events to the default <see cref="TraceEvent"/> channel.
     /// </summary>
     public static class TraceLog {
+        /// <summary>
+        /// Starts the logical operation nested to the current operation nested to the current one.
+        /// </summary>
         [Conditional("TRACE")]
         public static void StartLogicalOperation() {
             TraceContext.Instance.StartLogicalOperation();
+
         }
 
+        /// <summary>
+        /// Starts the logical operation with the specified name, this name is usefull in logs.
+        /// </summary>
+        /// <param name="name">Name.</param>
         [Conditional("TRACE")]
         public static void StartLogicalOperation(string name) {
             TraceContext.Instance.StartLogicalOperation(name);
         }
 
+        /// <summary>
+        /// Ends the logical operation and restores the previous one.
+        /// </summary>
         [Conditional("TRACE")]
         public static void EndLogicalOperation() {
-            TraceContext.Instance.EndLogicalOperation();
+            var op = TraceContext.Instance.EndLogicalOperation();
+            LogChannel<TraceEvent>.Default.LogEvent(new TraceEvent(op, TraceEventType.OperationCompleted,  String.Format("-{0} : {1}ms",op.Name, op.Duration)));
         }
 
+        /// <summary>
+        /// Writes an informational message.
+        /// </summary>
+        /// <param name="format">Format.</param>
+        /// <param name="arguments">Arguments.</param>
         [Conditional("TRACE")]
         public static void TraceInformation(string format, params object[] arguments) {
-            LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceEventType.Information, format, arguments));
+            LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Information, format, arguments));
         }
 
+        /// <summary>
+        /// Writes a warning message.
+        /// </summary>
+        /// <param name="format">Format.</param>
+        /// <param name="arguments">Arguments.</param>
         [Conditional("TRACE")]
         public static void TraceWarning(string format, params object[] arguments) {
-            LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceEventType.Warning, format, arguments));
+            LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Warning, format, arguments));
         }
 
         [Conditional("TRACE")]
         public static void TraceError(string format, params object[] arguments) {
-            LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceEventType.Error, format, arguments));
+            LogChannel<TraceEvent>.Default.LogEvent(TraceEvent.Create(TraceContext.Instance.CurrentOperation, TraceEventType.Error, format, arguments));
         }
 
         [Conditional("TRACE")]
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/Diagnostics/TraceSourceAttribute.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,10 @@
+using System;
+
+namespace Implab.Diagnostics {
+    /// <summary>
+    /// Used to mark class which uses <see cref="Trace{T}"/> class to trace it's events
+    /// </summary>
+    [AttributeUsage(AttributeTargets.Class)]
+    public class TraceSourceAttribute : Attribute {
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/FailedPromise.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,100 @@
+using System;
+using System.Reflection;
+
+namespace Implab {
+    public class FailedPromise : IPromise {
+        readonly Exception m_error;
+        public FailedPromise(Exception error) {
+            Safe.ArgumentNotNull(error, "error");
+            m_error = error;
+        }
+
+        #region IPromise implementation
+
+        public IPromise On(Action success, Action<Exception> error, Action<Exception> cancel) {
+            if (error != null) {
+                try {
+                    error(m_error);  
+                // Analysis disable once EmptyGeneralCatchClause
+                } catch {
+                }
+            }
+            return this;
+        }
+
+        public IPromise On(Action success, Action<Exception> error) {
+            if (error != null) {
+                try {
+                    error(m_error);  
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
+                }
+            }
+            return this;
+        }
+
+        public IPromise On(Action success) {
+            return this;
+        }
+
+        public IPromise On(Action handler, PromiseEventType events) {
+            if ((events & PromiseEventType.Error) != 0) {
+                try {
+                    handler();
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
+                }
+            }
+            return this;
+        }
+
+        public IPromise<T> Cast<T>() {
+            return (IPromise<T>)this;
+        }
+
+        public void Join() {
+            throw new TargetInvocationException(Error);
+        }
+
+        public void Join(int timeout) {
+            throw new TargetInvocationException(Error);
+        }
+
+        public virtual Type PromiseType {
+            get {
+                return typeof(void);
+            }
+        }
+
+        public bool IsResolved {
+            get {
+                return true;
+            }
+        }
+
+        public bool IsCancelled {
+            get {
+                return false;
+            }
+        }
+
+        public Exception Error {
+            get {
+                return m_error;
+            }
+        }
+
+        #endregion
+
+        #region ICancellable implementation
+
+        public void Cancel() {
+        }
+
+        public void Cancel(Exception reason) {
+        }
+
+        #endregion
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/FailedPromiseT.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,65 @@
+using System;
+using System.Reflection;
+
+namespace Implab {
+    public class FailedPromise<T> : FailedPromise, IPromise<T> {
+        public FailedPromise(Exception error) : base(error) {
+        }
+
+        public IPromise<T> On(Action<T> success, Action<Exception> error, Action<Exception> cancel) {
+            if (error != null) {
+                try {
+                    error(Error);  
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
+                }
+            }
+            return this;
+        }
+
+        public IPromise<T> On(Action<T> success, Action<Exception> error) {
+            if (error != null) {
+                try {
+                    error(Error);  
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
+                }
+            }
+            return this;
+        }
+
+        public IPromise<T> On(Action<T> success) {
+            return this;
+        }
+
+        T IPromise<T>.Join() {
+            throw new TargetInvocationException(Error);
+        }
+
+        T IPromise<T>.Join(int timeout) {
+            throw new TargetInvocationException(Error);
+        }
+
+
+        IPromise<T> IPromise<T>.On(Action success, Action<Exception> error, Action<Exception> cancel) {
+            On(success, error, cancel);
+            return this;
+        }
+
+        IPromise<T> IPromise<T>.On(Action success, Action<Exception> error) {
+            On(success, error);
+            return this;
+        }
+
+        IPromise<T> IPromise<T>.On(Action success) {
+            On(success);
+            return this;
+        }
+
+        IPromise<T> IPromise<T>.On(Action handler, PromiseEventType events) {
+            On(handler, events);
+            return this;
+        }
+    }
+}
+
--- a/Implab/Formats/JSON/JSONParser.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Formats/JSON/JSONParser.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -274,7 +274,7 @@
 
         protected override void Dispose(bool disposing) {
             if (disposing)
-                Safe.Dispose(m_scanner);
+                m_scanner.Dispose();
         }
 
         /// <summary>
--- a/Implab/Formats/JSON/JSONScanner.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Formats/JSON/JSONScanner.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -102,7 +102,7 @@
 
         protected override void Dispose(bool disposing) {
             if (disposing)
-                Safe.Dispose(m_scanner);
+                m_scanner.Dispose();
             base.Dispose(disposing);
         }
     }
--- a/Implab/Implab.csproj	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Implab.csproj	Tue Aug 22 12:45:01 2017 +0300
@@ -8,8 +8,6 @@
     <RootNamespace>Implab</RootNamespace>
     <AssemblyName>Implab</AssemblyName>
     <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
-    <ProductVersion>8.0.30703</ProductVersion>
-    <SchemaVersion>2.0</SchemaVersion>
   </PropertyGroup>
   <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
     <DebugSymbols>true</DebugSymbols>
@@ -74,15 +72,17 @@
     <Reference Include="mscorlib" />
   </ItemGroup>
   <ItemGroup>
+    <Compile Include="Components\StateChangeEventArgs.cs" />
     <Compile Include="CustomEqualityComparer.cs" />
     <Compile Include="Diagnostics\ConsoleTraceListener.cs" />
-    <Compile Include="Diagnostics\EventText.cs" />
     <Compile Include="Diagnostics\LogChannel.cs" />
     <Compile Include="Diagnostics\LogicalOperation.cs" />
     <Compile Include="Diagnostics\TextFileListener.cs" />
+    <Compile Include="Diagnostics\Trace.cs" />
     <Compile Include="Diagnostics\TraceLog.cs" />
     <Compile Include="Diagnostics\TraceEvent.cs" />
     <Compile Include="Diagnostics\TraceEventType.cs" />
+    <Compile Include="Diagnostics\TraceSourceAttribute.cs" />
     <Compile Include="ICancellable.cs" />
     <Compile Include="IProgressHandler.cs" />
     <Compile Include="IProgressNotifier.cs" />
@@ -98,9 +98,10 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="Parallels\AsyncPool.cs" />
     <Compile Include="Safe.cs" />
+    <Compile Include="SyncContextPromise.cs" />
     <Compile Include="ValueEventArgs.cs" />
     <Compile Include="PromiseExtensions.cs" />
-    <Compile Include="SyncContextPromise.cs" />
+    <Compile Include="SyncContextPromiseT.cs" />
     <Compile Include="Diagnostics\OperationContext.cs" />
     <Compile Include="Diagnostics\TraceContext.cs" />
     <Compile Include="Diagnostics\LogEventArgs.cs" />
@@ -194,6 +195,9 @@
     <Compile Include="Components\LazyAndWeak.cs" />
     <Compile Include="AbstractTask.cs" />
     <Compile Include="AbstractTaskT.cs" />
+    <Compile Include="FailedPromise.cs" />
+    <Compile Include="FailedPromiseT.cs" />
+    <Compile Include="Components\PollingComponent.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup />
@@ -201,14 +205,12 @@
     <MonoDevelop>
       <Properties>
         <Policies>
-          <CSharpFormattingPolicy IndentBlock="True" IndentBraces="False" IndentSwitchSection="False" IndentSwitchCaseSection="True" LabelPositioning="OneLess" NewLinesForBracesInTypes="True" NewLinesForBracesInMethods="True" NewLinesForBracesInProperties="False" NewLinesForBracesInAccessors="False" NewLinesForBracesInAnonymousMethods="False" NewLinesForBracesInControlBlocks="False" NewLinesForBracesInAnonymousTypes="False" NewLinesForBracesInObjectCollectionArrayInitializers="False" NewLinesForBracesInLambdaExpressionBody="False" NewLineForElse="False" NewLineForCatch="False" NewLineForFinally="False" NewLineForMembersInObjectInit="False" NewLineForMembersInAnonymousTypes="False" NewLineForClausesInQuery="False" SpacingAfterMethodDeclarationName="True" SpaceWithinMethodDeclarationParenthesis="False" SpaceBetweenEmptyMethodDeclarationParentheses="False" SpaceAfterMethodCallName="True" SpaceWithinMethodCallParentheses="False" SpaceBetweenEmptyMethodCallParentheses="False" SpaceAfterControlFlowStatementKeyword="True" SpaceWithinExpressionParentheses="False" SpaceWithinCastParentheses="False" SpaceWithinOtherParentheses="False" SpaceAfterCast="False" SpacesIgnoreAroundVariableDeclaration="False" SpaceBeforeOpenSquareBracket="True" SpaceBetweenEmptySquareBrackets="False" SpaceWithinSquareBrackets="False" SpaceAfterColonInBaseTypeDeclaration="True" SpaceAfterComma="True" SpaceAfterDot="False" SpaceAfterSemicolonsInForStatement="True" SpaceBeforeColonInBaseTypeDeclaration="True" SpaceBeforeComma="False" SpaceBeforeDot="False" SpaceBeforeSemicolonsInForStatement="False" SpacingAroundBinaryOperator="Single" WrappingPreserveSingleLine="True" WrappingKeepStatementsOnSingleLine="True" PlaceSystemDirectiveFirst="True" scope="text/x-csharp" />
-          <TextStylePolicy FileWidth="120" TabWidth="4" IndentWidth="4" RemoveTrailingWhitespace="True" NoTabsAfterNonTabs="False" TabsToSpaces="True" EolMarker="Unix" scope="text/x-csharp" />
+          <CSharpFormattingPolicy IndentSwitchBody="True" NamespaceBraceStyle="EndOfLine" ClassBraceStyle="EndOfLine" InterfaceBraceStyle="EndOfLine" StructBraceStyle="EndOfLine" EnumBraceStyle="EndOfLine" MethodBraceStyle="EndOfLine" ConstructorBraceStyle="EndOfLine" DestructorBraceStyle="EndOfLine" BeforeMethodDeclarationParentheses="False" BeforeMethodCallParentheses="False" BeforeConstructorDeclarationParentheses="False" NewLineBeforeConstructorInitializerColon="NewLine" NewLineAfterConstructorInitializerColon="SameLine" BeforeIndexerDeclarationBracket="False" BeforeDelegateDeclarationParentheses="False" NewParentheses="False" SpacesBeforeBrackets="False" inheritsSet="Mono" inheritsScope="text/x-csharp" scope="text/x-csharp" />
+          <TextStylePolicy FileWidth="120" EolMarker="Unix" inheritsSet="VisualStudio" inheritsScope="text/plain" scope="text/x-csharp" />
           <DotNetNamingPolicy DirectoryNamespaceAssociation="PrefixedHierarchical" ResourceNamePolicy="MSBuild" />
-          <TextStylePolicy FileWidth="120" TabWidth="4" TabsToSpaces="False" IndentWidth="4" RemoveTrailingWhitespace="True" NoTabsAfterNonTabs="False" EolMarker="Native" scope="application/xml" />
-          <XmlFormattingPolicy scope="application/xml">
-            <DefaultFormat OmitXmlDeclaration="False" NewLineChars="&#xA;" IndentContent="True" ContentIndentString="&#x9;" AttributesInNewLine="False" MaxAttributesPerLine="10" AttributesIndentString="&#x9;" WrapAttributes="False" AlignAttributes="False" AlignAttributeValues="False" QuoteChar="&quot;" SpacesBeforeAssignment="0" SpacesAfterAssignment="0" EmptyLinesBeforeStart="0" EmptyLinesAfterStart="0" EmptyLinesBeforeEnd="0" EmptyLinesAfterEnd="0" />
-          </XmlFormattingPolicy>
-          <TextStylePolicy FileWidth="120" TabWidth="4" TabsToSpaces="False" IndentWidth="4" RemoveTrailingWhitespace="True" NoTabsAfterNonTabs="False" EolMarker="Native" scope="text/plain" />
+          <TextStylePolicy FileWidth="120" TabsToSpaces="False" inheritsSet="VisualStudio" inheritsScope="text/plain" scope="application/xml" />
+          <XmlFormattingPolicy inheritsSet="Mono" inheritsScope="application/xml" scope="application/xml" />
+          <TextStylePolicy FileWidth="120" TabsToSpaces="False" inheritsSet="VisualStudio" inheritsScope="text/plain" scope="text/plain" />
           <NameConventionPolicy>
             <Rules>
               <NamingRule Name="Namespaces" AffectedEntity="Namespace" VisibilityMask="VisibilityMask" NamingStyle="PascalCase" IncludeInstanceMembers="True" IncludeStaticEntities="True" />
@@ -268,10 +270,5 @@
       </Properties>
     </MonoDevelop>
   </ProjectExtensions>
-  <ItemGroup>
-    <Folder Include="Components\" />
-    <Folder Include="Automaton\RegularExpressions\" />
-    <Folder Include="Formats\" />
-    <Folder Include="Formats\JSON\" />
-  </ItemGroup>
+  <ItemGroup />
 </Project>
\ No newline at end of file
--- a/Implab/Promise.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Promise.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -3,11 +3,10 @@
 
 namespace Implab {
     public class Promise : AbstractPromise, IDeferred {
-        public static readonly Promise SUCCESS;
+        public static readonly IPromise Success;
 
         static Promise() {
-            SUCCESS = new Promise();
-            SUCCESS.Resolve();
+            Success = new SuccessPromise();
         }
 
         public void Resolve() {
@@ -16,7 +15,11 @@
 
         public void Reject(Exception error) {
             SetError(error);
-        }
+        }
+
+        public static IPromise FromException(Exception exception) {
+            return new FailedPromise(exception);
+        }
     }
 }
 
--- a/Implab/PromiseExtensions.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/PromiseExtensions.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -2,7 +2,8 @@
 using System;
 using Implab.Diagnostics;
 using System.Collections.Generic;
-
+using System.Linq;
+
 namespace Implab {
     public static class PromiseExtensions {
         public static IPromise<T> DispatchToCurrentContext<T>(this IPromise<T> that) {
@@ -46,7 +47,7 @@
         /// <param name="cleanup">Cleanup.</param>
         /// <typeparam name="TPromise">The 1st type parameter.</typeparam>
         /// <typeparam name="T">The 2nd type parameter.</typeparam>
-        public static TPromise EnsureDispatched<TPromise,T>(this TPromise that, IPromise<T> head, Action<T> cleanup) where TPromise : IPromise{
+        public static TPromise EnsureDispatched<TPromise, T>(this TPromise that, IPromise<T> head, Action<T> cleanup) where TPromise : IPromise {
             Safe.ArgumentNotNull(that, "that");
             Safe.ArgumentNotNull(head, "head");
 
@@ -55,12 +56,37 @@
             return that;
         }
 
-        public static AsyncCallback AsyncCallback<T>(this Promise<T> that, Func<IAsyncResult,T> callback) {
+        /// <summary>
+        /// Adds a cancellation point to the chain of promises. When a cancellation request reaches the cancellation point the operation is
+        /// cancelled immediatelly, and the request is passed towards. If the operation at the higher level can not be cancelled is't result
+        /// will be collected with <paramref name="cleanup"/> callback.
+        /// </summary>
+        /// <typeparam name="T">The type of the promise result.</typeparam>
+        /// <param name="that">The promise to which the cancellation point should be attached.</param>
+        /// <param name="cleanup">The callback which is used to cleanup the result of the operation if the cancellation point is cancelled already.</param>
+        /// <returns>The promise</returns>
+        public static IPromise<T> CancellationPoint<T>(this IPromise<T> that, Action<T> cleanup) {
+            var meduim = new Promise<T>();
+
+            that.On(meduim.Resolve, meduim.Reject, meduim.CancelOperation);
+
+            meduim.CancellationRequested(that.Cancel);
+            meduim.CancellationRequested(meduim.CancelOperation);
+
+            if (cleanup != null)
+                meduim.On((Action<T>)null, null, (e) => {
+                    that.On(cleanup);
+                });
+
+            return meduim;
+        }
+
+        public static AsyncCallback AsyncCallback<T>(this Promise<T> that, Func<IAsyncResult, T> callback) {
             Safe.ArgumentNotNull(that, "that");
             Safe.ArgumentNotNull(callback, "callback");
             var op = TraceContext.Instance.CurrentOperation;
             return ar => {
-                TraceContext.Instance.EnterLogicalOperation(op,false);
+                TraceContext.Instance.EnterLogicalOperation(op, false);
                 try {
                     that.Resolve(callback(ar));
                 } catch (Exception err) {
@@ -73,8 +99,8 @@
 
         static void CancelByTimeoutCallback(object cookie) {
             ((ICancellable)cookie).Cancel(new TimeoutException());
-        }
-
+        }
+
         /// <summary>
         /// Cancells promise after the specified timeout is elapsed.
         /// </summary>
@@ -88,7 +114,21 @@
             return that;
         }
 
-        public static IPromise Bundle(this ICollection<IPromise> that) {
+        public static IPromise PromiseAll(this IEnumerable<IPromise> that) {
+            Safe.ArgumentNotNull(that, "that");
+            return PromiseAll(that.ToList());
+        }
+
+        public static IPromise<T[]> PromiseAll<T>(this IEnumerable<IPromise<T>> that) {
+            return PromiseAll(that, null);
+        }
+
+        public static IPromise<T[]> PromiseAll<T>(this IEnumerable<IPromise<T>> that, Action<T> cleanup) {
+            Safe.ArgumentNotNull(that, "that");
+            return PromiseAll(that.ToList(), cleanup);
+        }
+
+        public static IPromise PromiseAll(this ICollection<IPromise> that) {
             Safe.ArgumentNotNull(that, "that");
 
             int count = that.Count;
@@ -101,7 +141,7 @@
             }
 
             medium.On(() => {
-                foreach(var p2 in that)
+                foreach (var p2 in that)
                     p2.Cancel();
             }, PromiseEventType.ErrorOrCancel);
 
@@ -128,17 +168,35 @@
             return medium;
         }
 
-        public static IPromise<T[]> Bundle<T>(this ICollection<IPromise<T>> that) {
-            Safe.ArgumentNotNull(that, "that");
+        public static IPromise<T[]> PromiseAll<T>(this ICollection<IPromise<T>> that) {
+            return PromiseAll(that, null);
+        }
 
+        /// <summary>
+        /// Creates a new promise which will be satisfied when all promises are satisfied.
+        /// </summary>
+        /// <typeparam name="T"></typeparam>
+        /// <param name="that"></param>
+        /// <param name="cleanup">A callback used to cleanup already resolved promises in case of an error</param>
+        /// <returns></returns>
+        public static IPromise<T[]> PromiseAll<T>(this ICollection<IPromise<T>> that, Action<T> cleanup) {
+            Safe.ArgumentNotNull(that, "that");
+
             int count = that.Count;
+
+            if (count == 0)
+                return Promise<T[]>.FromResult(new T[0]);
+
             int errors = 0;
             var medium = new Promise<T[]>();
             var results = new T[that.Count];
 
             medium.On(() => {
-                foreach(var p2 in that)
-                    p2.Cancel();
+                foreach (var p2 in that) {
+                    p2.Cancel();
+                    if (cleanup != null)
+                        p2.On(cleanup);
+                }
             }, PromiseEventType.ErrorOrCancel);
 
             int i = 0;
@@ -205,12 +263,38 @@
 
         public static IPromise<T2> Then<T, T2>(this IPromise<T> that, Func<T, T2> success, Func<Exception, T2> error, Func<Exception, T2> cancel) {
             Safe.ArgumentNotNull(that, "that");
-            var d = new FuncTask<T,T2>(success, error, cancel, false);
+            Safe.ArgumentNotNull(success, "success");
+
+            var d = new FuncTask<T, T2>(success, error, cancel, false);
             that.On(d.Resolve, d.Reject, d.CancelOperation);
             d.CancellationRequested(that.Cancel);
             return d;
         }
 
+        public static IPromise<T> Then<T>(this IPromise<T> that, Action<T> success, Func<Exception, T> error, Func<Exception, T> cancel) {
+            Safe.ArgumentNotNull(that, "that");
+            var d = new FuncTask<T, T>(
+                x => {
+                    success(x);
+                    return x;
+                },
+                error,
+                cancel,
+                false
+            );
+            that.On(d.Resolve, d.Reject, d.CancelOperation);
+            d.CancellationRequested(that.Cancel);
+            return d;
+        }
+
+        public static IPromise<T> Then<T>(this IPromise<T> that, Action<T> success, Func<Exception, T> error) {
+            return Then(that, success, error, null);
+        }
+
+        public static IPromise<T> Then<T>(this IPromise<T> that, Action<T> success) {
+            return Then(that, success, null, null);
+        }
+
         public static IPromise<T2> Then<T, T2>(this IPromise<T> that, Func<T, T2> success, Func<Exception, T2> error) {
             return Then(that, success, error, null);
         }
@@ -219,8 +303,79 @@
             return Then(that, success, null, null);
         }
 
+        public static IPromise<T> Always<T>(this IPromise<T> that, Action handler) {
+            Func<Exception, T> errorOrCancel;
+            if (handler != null)
+                errorOrCancel = e => {
+                    handler();
+                    throw new PromiseTransientException(e);
+                };
+            else
+                errorOrCancel = null;
+
+            return Then(
+                that,
+                x => {
+                    handler();
+                    return x;
+                },
+                errorOrCancel,
+                errorOrCancel);
+        }
+
+        public static IPromise Always(this IPromise that, Action handler) {
+            Action<Exception> errorOrCancel;
+            if (handler != null)
+                errorOrCancel = e => {
+                    handler();
+                    throw new PromiseTransientException(e);
+                };
+            else
+                errorOrCancel = null;
+
+            return Then(
+                that,
+                handler,
+                errorOrCancel,
+                errorOrCancel);
+        }
+
+        public static IPromise Error(this IPromise that, Action<Exception> handler, bool handleCancellation) {
+            Action<Exception> errorOrCancel;
+            if (handler != null)
+                errorOrCancel = e => {
+                    handler(e);
+                    throw new PromiseTransientException(e);
+                };
+            else
+                errorOrCancel = null;
+
+            return Then(that, null, errorOrCancel, handleCancellation ? errorOrCancel : null);
+        }
+
+        public static IPromise Error(this IPromise that, Action<Exception> handler) {
+            return Error(that, handler, false);
+        }
+
+        public static IPromise<T> Error<T>(this IPromise<T> that, Action<Exception> handler, bool handleCancellation) {
+            Func<Exception, T> errorOrCancel;
+            if (handler != null)
+                errorOrCancel = e => {
+                    handler(e);
+                    throw new PromiseTransientException(e);
+                };
+            else
+                errorOrCancel = null;
+
+            return Then(that, null, errorOrCancel, handleCancellation ? errorOrCancel : null);
+        }
+
+        public static IPromise<T> Error<T>(this IPromise<T> that, Action<Exception> handler) {
+            return Error(that, handler, false);
+        }
+
         #region chain traits
-        public static IPromise Chain(this IPromise that, Func<IPromise> success, Func<Exception,IPromise> error, Func<Exception,IPromise> cancel) {
+        public static IPromise Chain(this IPromise that, Func<IPromise> success, Func<Exception, IPromise> error, Func<Exception, IPromise> cancel) {
             Safe.ArgumentNotNull(that, "that");
 
             var d = new ActionChainTask(success, error, cancel, false);
@@ -229,7 +384,7 @@
             return d;
         }
 
-        public static IPromise Chain(this IPromise that, Func<IPromise> success, Func<Exception,IPromise> error) {
+        public static IPromise Chain(this IPromise that, Func<IPromise> success, Func<Exception, IPromise> error) {
             return Chain(that, success, error, null);
         }
 
@@ -257,7 +412,7 @@
 
         public static IPromise<T2> Chain<T, T2>(this IPromise<T> that, Func<T, IPromise<T2>> success, Func<Exception, IPromise<T2>> error, Func<Exception, IPromise<T2>> cancel) {
             Safe.ArgumentNotNull(that, "that");
-            var d = new FuncChainTask<T,T2>(success, error, cancel, false);
+            var d = new FuncChainTask<T, T2>(success, error, cancel, false);
             that.On(d.Resolve, d.Reject, d.CancelOperation);
             if (success != null)
                 d.CancellationRequested(that.Cancel);
@@ -270,20 +425,51 @@
 
         public static IPromise<T2> Chain<T, T2>(this IPromise<T> that, Func<T, IPromise<T2>> success) {
             return Chain(that, success, null, null);
-        }
-
+        }
+
         #endregion
-
-            
-        #if NET_4_5
-
+
+        public static IPromise<T2> Guard<T, T2>(this IPromise<T> that, Func<IPromise<T>, IPromise<T2>> continuation, Action<T> cleanup) {
+            Safe.ArgumentNotNull(that, "that");
+            Safe.ArgumentNotNull(continuation, "continuation");
+            return continuation(that).Error((err) => {
+                that.On(cleanup);
+            }, true);
+        }
+
+#if NET_4_5
+
         public static PromiseAwaiter<T> GetAwaiter<T>(this IPromise<T> that) {
             Safe.ArgumentNotNull(that, "that");
 
             return new PromiseAwaiter<T>(that);
-        }
+        }
+
+        public static PromiseAwaiter GetAwaiter(this IPromise that) {
+            Safe.ArgumentNotNull(that, "that");
 
-        #endif
+            return new PromiseAwaiter(that);
+        }
+
+        public static IPromise BoundCancellationToken(this IPromise that, CancellationToken ct) {
+            Safe.ArgumentNotNull(that, "that");
+            ct.Register(that.Cancel);
+            return that.Then(null, null, (err) => {
+                ct.ThrowIfCancellationRequested();
+                throw new PromiseTransientException(err);
+            });
+        }
+
+        public static IPromise<T> BoundCancellationToken<T>(this IPromise<T> that, CancellationToken ct) {
+            Safe.ArgumentNotNull(that, "that");
+            ct.Register(that.Cancel);
+            return that.Then(null, null, (err) => {
+                ct.ThrowIfCancellationRequested();
+                throw new PromiseTransientException(err);
+            });
+        }
+
+#endif
     }
 }
 
--- a/Implab/PromiseT.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/PromiseT.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -45,9 +45,7 @@
         }
 
         public static IPromise<T> FromException(Exception error) {
-            var p = new Promise<T>();
-            p.Reject(error);
-            return p;
+            return new FailedPromise<T>(error);
         }
 
         public virtual void Resolve(T value) {
--- a/Implab/Safe.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/Safe.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -4,6 +4,11 @@
 using System.Text;
 using System.Text.RegularExpressions;
 using System.Diagnostics;
+using System.Collections;
+
+#if NET_4_5
+using System.Threading.Tasks;
+#endif
 
 namespace Implab
 {
@@ -60,62 +65,75 @@
             }
         }
 
-        public static void Dispose(object obj) {
-            var d = obj as IDisposable;
-            if (d != null)
-                d.Dispose();
-        }
-
-        [DebuggerStepThrough]
-        public static IPromise<T> WrapPromise<T>(Func<T> action) {
-            ArgumentNotNull(action, "action");
-
-            var p = new Promise<T>();
-            try {
-                p.Resolve(action());
-            } catch (Exception err) {
-                p.Reject(err);
-            }
-
-            return p;
+        public static void DisposeCollection(IEnumerable<IDisposable> objects) {
+            foreach (var d in objects)
+                Dispose(d);
         }
 
-        [DebuggerStepThrough]
-        public static IPromise WrapPromise(Action action) {
-            ArgumentNotNull(action, "action");
-
-            var p = new Promise();
-            try {
-                action();
-                p.Resolve();
-            } catch (Exception err) {
-                p.Reject(err);
-            }
-
-            return p;
+        public static void DisposeCollection(IEnumerable objects) {
+            foreach (var d in objects)
+                Dispose(d);
         }
 
-        [DebuggerStepThrough]
-        public static IPromise InvokePromise(Func<IPromise> action) {
-            ArgumentNotNull(action, "action");
-
-            try {
-                var p = action();
-                if (p == null) {
-                    var d = new Promise();
-                    d.Reject(new Exception("The action returned null"));
-                    p = d;
-                }
-                return p;
-            } catch (Exception err) {
-                var p = new Promise();
-                p.Reject(err);
-                return p;
+        public static void Dispose(object obj) {
+            if (obj is IDisposable) {
+                Dispose((IDisposable)obj);
+            } else if (obj is IEnumerable) {
+                DisposeCollection((IEnumerable)obj);
             }
         }
 
         [DebuggerStepThrough]
-        public static IPromise<T> InvokePromise<T>(Func<IPromise<T>> action) {
+        public static void DispatchEvent<T>(this EventHandler<T> handler, object sender, T args) {
+            if (handler != null)
+                handler(sender, args);
+        }
+
+        [DebuggerStepThrough]
+        public static void DispatchEvent(this EventHandler handler, object sender, EventArgs args) {
+            if (handler != null)
+                handler(sender, args);
+        }
+
+        [DebuggerStepThrough]
+        public static IPromise<T> Run<T>(Func<T> action) {
+            ArgumentNotNull(action, "action");
+
+            try {
+                return Promise<T>.FromResult(action());
+            } catch (Exception err) {
+                return Promise<T>.FromException(err);
+            }
+        }
+
+        [DebuggerStepThrough]
+        public static IPromise Run(Action action) {
+            ArgumentNotNull(action, "action");
+
+            try {
+                action();
+                return Promise.Success;
+            } catch (Exception err) {
+                return new FailedPromise(err);
+            }
+        }
+
+        [DebuggerStepThrough]
+        public static IPromise Run(Func<IPromise> action) {
+            ArgumentNotNull(action, "action");
+
+            try {
+                return action() ?? new FailedPromise(new Exception("The action returned null"));
+            } catch (Exception err) {
+                return new FailedPromise(err);
+            }
+        }
+
+        public static void NoWait(IPromise promise) {
+        }
+
+        [DebuggerStepThrough]
+        public static IPromise<T> Run<T>(Func<IPromise<T>> action) {
             ArgumentNotNull(action, "action");
 
             try {
@@ -124,5 +142,11 @@
                 return Promise<T>.FromException(err);
             }
         }
+
+#if NET_4_5
+        public static void NoWait(Task t) {
+        }
+#endif
+
     }
 }
--- a/Implab/SuccessPromise.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/SuccessPromise.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -8,14 +8,8 @@
             if (success != null) {
                 try {
                     success();
-                } catch(Exception err) {
-                    if (error != null) {
-                        try {
-                            error(err);
-                        // Analysis disable once EmptyGeneralCatchClause
-                        } catch {
-                        }
-                    }
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
                 }
             }
             return this;
@@ -25,14 +19,8 @@
             if (success != null) {
                 try {
                     success();
-                } catch(Exception err) {
-                    if (error != null) {
-                        try {
-                            error(err);
-                            // Analysis disable once EmptyGeneralCatchClause
-                        } catch {
-                        }
-                    }
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
                 }
             }
             return this;
--- a/Implab/SuccessPromiseT.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/SuccessPromiseT.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -12,14 +12,8 @@
             if (success != null) {
                 try {
                     success(m_value);
-                } catch(Exception err) {
-                    if (error != null) {
-                        try {
-                            error(err);
-                            // Analysis disable once EmptyGeneralCatchClause
-                        } catch {
-                        }
-                    }
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
                 }
             }
             return this;
@@ -29,14 +23,8 @@
             if (success != null) {
                 try {
                     success(m_value);
-                } catch(Exception err) {
-                    if (error != null) {
-                        try {
-                            error(err);
-                            // Analysis disable once EmptyGeneralCatchClause
-                        } catch {
-                        }
-                    }
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
                 }
             }
             return this;
@@ -65,14 +53,8 @@
             if (success != null) {
                 try {
                     success();
-                } catch(Exception err) {
-                    if (error != null) {
-                        try {
-                            error(err);
-                            // Analysis disable once EmptyGeneralCatchClause
-                        } catch {
-                        }
-                    }
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
                 }
             }
             return this;
@@ -82,14 +64,8 @@
             if (success != null) {
                 try {
                     success();
-                } catch(Exception err) {
-                    if (error != null) {
-                        try {
-                            error(err);
-                            // Analysis disable once EmptyGeneralCatchClause
-                        } catch {
-                        }
-                    }
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
                 }
             }
             return this;
--- a/Implab/SyncContextPromise.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/Implab/SyncContextPromise.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -1,18 +1,21 @@
-using System.Threading;
-using System;
-
-namespace Implab {
-    public class SyncContextPromise<T> : Promise<T> {
-        readonly SynchronizationContext m_context;
-
-        public SyncContextPromise(SynchronizationContext context) {
-            Safe.ArgumentNotNull(context, "context");
-            m_context = context;
-        }
-
-        protected override void SignalHandler(HandlerDescriptor handler, int signal) {
-            m_context.Post(x => base.SignalHandler(handler, signal), null);
-        }
-    }
-}
-
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading;
+
+namespace Implab {
+    public class SyncContextPromise : Promise {
+        readonly SynchronizationContext m_context;
+
+        public SyncContextPromise(SynchronizationContext context) {
+            Safe.ArgumentNotNull(context, "context");
+
+            m_context = context;
+        }
+
+        protected override void SignalHandler(HandlerDescriptor handler, int signal) {
+            m_context.Post(x => base.SignalHandler(handler, signal), null);
+        }
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/SyncContextPromiseT.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -0,0 +1,18 @@
+using System.Threading;
+using System;
+
+namespace Implab {
+    public class SyncContextPromise<T> : Promise<T> {
+        readonly SynchronizationContext m_context;
+
+        public SyncContextPromise(SynchronizationContext context) {
+            Safe.ArgumentNotNull(context, "context");
+            m_context = context;
+        }
+
+        protected override void SignalHandler(HandlerDescriptor handler, int signal) {
+            m_context.Post(x => base.SignalHandler(handler, signal), null);
+        }
+    }
+}
+
--- a/MonoPlay/Program.cs	Tue Aug 22 09:35:54 2017 +0300
+++ b/MonoPlay/Program.cs	Tue Aug 22 12:45:01 2017 +0300
@@ -1,42 +1,42 @@
-using System;
-using Implab;
-using System.Threading.Tasks;
-using Implab.Formats.JSON;
-using System.IO;
-using System.Text.Json;
+using Implab;
+using Implab.Parallels;
+using Implab.Diagnostics;
+using System.Threading;
 
 namespace MonoPlay {
     class MainClass {
 
 
         public static void Main(string[] args) {
-            if (args == null)
-                throw new ArgumentNullException("args");
-            int t1, t2;
+            var pool = new WorkerPool(10);
+
+            var listerner = new ConsoleTraceListener();
+            listerner.Subscribe<TraceEvent>();
+
+            TraceLog.StartLogicalOperation("Main");
 
-            for (int i = 0; i < 2; i++) {
-                t1 = Environment.TickCount;
-                int elements =0;
-                using (var reader = new JSONParser(File.OpenText("/home/sergey/temp/citylots.json"))) {
-                    while (reader.Read())
-                        elements++;
-                }
+
+            var d = pool.Invoke(() => {
+                TraceLog.StartLogicalOperation("Worker");
+                Thread.Sleep(100);
+                TraceLog.TraceInformation("worker done");
+                TraceLog.EndLogicalOperation();
+            });
 
-                t2 = Environment.TickCount;
-                Console.WriteLine("attempt {0} done: {1} ms, {2:.00} Mb, {3} GC, Elements: {4}",i+1, t2 - t1, GC.GetTotalMemory(false) / (1024*1024), GC.CollectionCount(0), elements );
-            }
+            var op = TraceContext.Instance.CurrentOperation;
+            ThreadPool.QueueUserWorkItem((o) => {
+                TraceContext.Instance.EnterLogicalOperation(op, false);
+                TraceLog.StartLogicalOperation("Thread");
+                Thread.Sleep(100);
+                TraceLog.TraceInformation("thread done");
+                TraceLog.EndLogicalOperation();
+            });
 
-            Console.WriteLine("Syste.Text.Json");
-            var paraser = new JsonParser();
-            for (int i = 0; i < 2; i++) {
-                t1 = Environment.TickCount;
-                using (var reader = File.OpenText("/home/sergey/temp/citylots.json")) {
-                    paraser.Parse(reader);
-                }
+            TraceLog.TraceInformation("main done");
+            TraceLog.EndLogicalOperation();
 
-                t2 = Environment.TickCount;
-                Console.WriteLine("attempt {0} done: {1} ms, {2:.00} Mb, {3} GC, ",i+1, t2 - t1, GC.GetTotalMemory(false) / (1024*1024), GC.CollectionCount(0));
-            }
+            d.Join();
+
 
 
         }