##// END OF EJS Templates
Code review for RunnableComponent...
cin -
r210:5dc21f6a3222 v2
parent child
Show More
@@ -0,0 +1,52
1 using System;
2 using System.Reflection;
3 using System.Threading;
4 using Implab.Parallels;
5 using Implab.Components;
6
7 #if MONO
8
9 using NUnit.Framework;
10 using TestClassAttribute = NUnit.Framework.TestFixtureAttribute;
11 using TestMethodAttribute = NUnit.Framework.TestAttribute;
12 using AssertFailedException = NUnit.Framework.AssertionException;
13 #else
14
15 using Microsoft.VisualStudio.TestTools.UnitTesting;
16
17 #endif
18 namespace Implab.Fx.Test {
19 [TestClass]
20 public class StaApartmentTests {
21 [TestMethod]
22 public void CreateDestroyApartment() {
23 var apartment = new StaApartment();
24 try {
25 Assert.IsNotNull(apartment.SyncContext);
26 Assert.Fail();
27 } catch (InvalidOperationException) {
28 // OK
29 }
30
31 apartment.Start().Join();
32 Assert.AreEqual(apartment.State, ExecutionState.Running);
33
34 Assert.IsNotNull(apartment.SyncContext);
35 apartment.Stop().Join();
36
37 Assert.IsTrue(apartment.State == ExecutionState.Disposed);
38 }
39
40 [TestMethod]
41 public void InvokeInApartment() {
42 var apartment = new StaApartment();
43
44 apartment.Start().Join();
45
46 var apType = apartment.Invoke(() => { return Thread.CurrentThread.GetApartmentState(); }).Join();
47 Assert.AreEqual(apType, ApartmentState.STA);
48
49 apartment.Stop().Join();
50 }
51 }
52 }
@@ -0,0 +1,188
1 using Implab.Components;
2 using Implab.Diagnostics;
3 using Implab.Parallels;
4 using System;
5 using System.Collections.Generic;
6 using System.Linq;
7 using System.Text;
8 using System.Threading;
9 using System.Threading.Tasks;
10 using System.Windows.Forms;
11
12 namespace Implab.Fx {
13 public class StaApartment : RunnableComponent {
14 readonly Thread m_worker;
15 SynchronizationContext m_syncContext;
16 readonly Promise m_threadStarted;
17 readonly Promise m_threadTerminated;
18
19 public StaApartment() : base(true) {
20 m_threadStarted = new Promise();
21 m_threadTerminated = new Promise();
22
23 m_worker = new Thread(WorkerEntry);
24 m_worker.SetApartmentState(ApartmentState.STA);
25 m_worker.IsBackground = true;
26 m_worker.Name = "STA managed aparment";
27 }
28
29 public SynchronizationContext SyncContext {
30 get {
31 if (m_syncContext == null)
32 throw new InvalidOperationException();
33 return m_syncContext;
34 }
35 }
36
37 public IPromise Invoke(Action<ICancellationToken> action) {
38 Safe.ArgumentNotNull(action, "action");
39
40 if (m_syncContext == null)
41 throw new InvalidOperationException();
42 var p = new Promise();
43 var lop = TraceContext.Instance.CurrentOperation;
44
45 m_syncContext.Post(x => {
46 TraceContext.Instance.EnterLogicalOperation(lop, false);
47 try {
48 if (p.CancelOperationIfRequested())
49 return;
50
51 action(p);
52 p.Resolve();
53 } catch (Exception e) {
54 p.Reject(e);
55 } finally {
56 TraceContext.Instance.Leave();
57 }
58 }, null);
59
60 return p;
61 }
62
63 public IPromise<T> Invoke<T>(Func<ICancellationToken, T> action) {
64 Safe.ArgumentNotNull(action, "action");
65
66 if (m_syncContext == null)
67 throw new InvalidOperationException();
68 var p = new Promise<T>();
69 var lop = TraceContext.Instance.CurrentOperation;
70
71 m_syncContext.Post(x => {
72 TraceContext.Instance.EnterLogicalOperation(lop, false);
73 try {
74 if (p.CancelOperationIfRequested())
75 return;
76 p.Resolve(action(p));
77 } catch (Exception e) {
78 p.Reject(e);
79 } finally {
80 TraceContext.Instance.Leave();
81 }
82 }, null);
83
84 return p;
85 }
86
87 public IPromise Invoke(Action action) {
88 Safe.ArgumentNotNull(action, "action");
89
90 if (m_syncContext == null)
91 throw new InvalidOperationException();
92 var p = new Promise();
93 var lop = TraceContext.Instance.CurrentOperation;
94
95 m_syncContext.Post(x => {
96 TraceContext.Instance.EnterLogicalOperation(lop, false);
97 try {
98 if (p.CancelOperationIfRequested())
99 return;
100 action();
101 p.Resolve();
102 } catch (Exception e) {
103 p.Reject(e);
104 } finally {
105 TraceContext.Instance.Leave();
106 }
107 }, null);
108
109 return p;
110 }
111
112 public IPromise<T> Invoke<T>(Func<T> action) {
113 Safe.ArgumentNotNull(action, "action");
114
115 if (m_syncContext == null)
116 throw new InvalidOperationException();
117 var p = new Promise<T>();
118 var lop = TraceContext.Instance.CurrentOperation;
119
120 m_syncContext.Post(x => {
121 TraceContext.Instance.EnterLogicalOperation(lop, false);
122 try {
123 if (p.CancelOperationIfRequested())
124 return;
125 p.Resolve(action());
126 } catch (Exception e) {
127 p.Reject(e);
128 } finally {
129 TraceContext.Instance.Leave();
130 }
131 }, null);
132
133 return p;
134 }
135
136
137 /// <summary>
138 /// Starts the apartment thread
139 /// </summary>
140 /// <returns>Promise which will be fullfiled when the syncronization
141 /// context will be ready to accept tasks.</returns>
142 protected override IPromise OnStart() {
143 m_worker.Start();
144 return m_threadStarted;
145 }
146
147 /// <summary>
148 /// Posts quit message to the message loop of the apartment
149 /// </summary>
150 /// <returns>Promise</returns>
151 protected override IPromise OnStop() {
152 m_syncContext.Post(x => Application.ExitThread(), null);
153 return m_threadTerminated;
154 }
155
156 void WorkerEntry() {
157 m_syncContext = new WindowsFormsSynchronizationContext();
158 SynchronizationContext.SetSynchronizationContext(m_syncContext);
159
160 m_threadStarted.Resolve();
161
162 Application.OleRequired();
163 Application.Run();
164
165 try {
166 OnShutdown();
167 m_threadTerminated.Resolve();
168 } catch(Exception err) {
169 m_threadTerminated.Reject(err);
170 }
171 }
172
173 /// <summary>
174 /// Called from the STA apartment after the message loop is terminated, override this
175 /// method to handle apartment cleanup.
176 /// </summary>
177 protected virtual void OnShutdown() {
178 }
179
180 protected override void Dispose(bool disposing) {
181 if (disposing) {
182 if (!m_threadTerminated.IsResolved)
183 m_syncContext.Post(x => Application.ExitThread(), null);
184 }
185 base.Dispose(disposing);
186 }
187 }
188 }
@@ -1,114 +1,115
1 1 ο»Ώ<?xml version="1.0" encoding="utf-8"?>
2 2 <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 3 <PropertyGroup>
4 4 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
5 5 <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
6 6 <ProductVersion>8.0.30703</ProductVersion>
7 7 <SchemaVersion>2.0</SchemaVersion>
8 8 <ProjectGuid>{2F31E405-E267-4195-A05D-574093C21209}</ProjectGuid>
9 9 <OutputType>Library</OutputType>
10 10 <AppDesignerFolder>Properties</AppDesignerFolder>
11 11 <RootNamespace>Implab.Fx.Test</RootNamespace>
12 12 <AssemblyName>Implab.Fx.Test</AssemblyName>
13 13 <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
14 14 <FileAlignment>512</FileAlignment>
15 15 <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
16 16 <TargetFrameworkProfile />
17 17 </PropertyGroup>
18 18 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
19 19 <DebugSymbols>true</DebugSymbols>
20 20 <DebugType>full</DebugType>
21 21 <Optimize>false</Optimize>
22 22 <OutputPath>bin\Debug\</OutputPath>
23 23 <DefineConstants>DEBUG;TRACE</DefineConstants>
24 24 <ErrorReport>prompt</ErrorReport>
25 25 <WarningLevel>4</WarningLevel>
26 26 <Prefer32Bit>false</Prefer32Bit>
27 27 </PropertyGroup>
28 28 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
29 29 <DebugType>pdbonly</DebugType>
30 30 <Optimize>true</Optimize>
31 31 <OutputPath>bin\Release\</OutputPath>
32 32 <DefineConstants>TRACE</DefineConstants>
33 33 <ErrorReport>prompt</ErrorReport>
34 34 <WarningLevel>4</WarningLevel>
35 35 <Prefer32Bit>false</Prefer32Bit>
36 36 </PropertyGroup>
37 37 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug 4.5|AnyCPU' ">
38 38 <DebugSymbols>true</DebugSymbols>
39 39 <DebugType>full</DebugType>
40 40 <Optimize>false</Optimize>
41 41 <OutputPath>bin\Debug\</OutputPath>
42 42 <DefineConstants>DEBUG;TRACE</DefineConstants>
43 43 <ErrorReport>prompt</ErrorReport>
44 44 <WarningLevel>4</WarningLevel>
45 45 <Prefer32Bit>false</Prefer32Bit>
46 46 </PropertyGroup>
47 47 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release 4.5|AnyCPU' ">
48 48 <DebugType>pdbonly</DebugType>
49 49 <Optimize>true</Optimize>
50 50 <OutputPath>bin\Release\</OutputPath>
51 51 <DefineConstants>TRACE</DefineConstants>
52 52 <ErrorReport>prompt</ErrorReport>
53 53 <WarningLevel>4</WarningLevel>
54 54 <Prefer32Bit>false</Prefer32Bit>
55 55 </PropertyGroup>
56 56 <ItemGroup>
57 57 <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
58 58 <Reference Include="System" />
59 59 <Reference Include="System.Core">
60 60 <RequiredTargetFramework>3.5</RequiredTargetFramework>
61 61 </Reference>
62 62 <Reference Include="System.Data" />
63 63 <Reference Include="System.Drawing" />
64 64 <Reference Include="System.Windows.Forms" />
65 65 <Reference Include="System.Xml" />
66 66 <Reference Include="WindowsBase" />
67 67 </ItemGroup>
68 68 <ItemGroup>
69 69 <Compile Include="Properties\AssemblyInfo.cs" />
70 70 <Compile Include="OverlayTest.cs" />
71 71 <Compile Include="Sample\MainForm.cs">
72 72 <SubType>Form</SubType>
73 73 </Compile>
74 74 <Compile Include="Sample\MainForm.Designer.cs">
75 75 <DependentUpon>MainForm.cs</DependentUpon>
76 76 </Compile>
77 77 <Compile Include="Sample\OverlayForm.cs">
78 78 <SubType>Form</SubType>
79 79 </Compile>
80 80 <Compile Include="Sample\OverlayForm.Designer.cs">
81 81 <DependentUpon>OverlayForm.cs</DependentUpon>
82 82 </Compile>
83 <Compile Include="StaApartmentTests.cs" />
83 84 </ItemGroup>
84 85 <ItemGroup>
85 86 <EmbeddedResource Include="Sample\MainForm.resx">
86 87 <DependentUpon>MainForm.cs</DependentUpon>
87 88 <LogicalName>
88 89 </LogicalName>
89 90 </EmbeddedResource>
90 91 <EmbeddedResource Include="Sample\OverlayForm.resx">
91 92 <DependentUpon>OverlayForm.cs</DependentUpon>
92 93 <LogicalName>
93 94 </LogicalName>
94 95 </EmbeddedResource>
95 96 </ItemGroup>
96 97 <ItemGroup>
97 98 <ProjectReference Include="..\Implab.Fx\Implab.Fx.csproj">
98 99 <Project>{06E706F8-6881-43EB-927E-FFC503AF6ABC}</Project>
99 100 <Name>Implab.Fx</Name>
100 101 </ProjectReference>
101 102 <ProjectReference Include="..\Implab\Implab.csproj">
102 103 <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project>
103 104 <Name>Implab</Name>
104 105 </ProjectReference>
105 106 </ItemGroup>
106 107 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
107 108 <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
108 109 Other similar extension points exist, see Microsoft.Common.targets.
109 110 <Target Name="BeforeBuild">
110 111 </Target>
111 112 <Target Name="AfterBuild">
112 113 </Target>
113 114 -->
114 115 </Project> No newline at end of file
@@ -1,88 +1,89
1 1 ο»Ώ<?xml version="1.0" encoding="utf-8"?>
2 2 <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 3 <PropertyGroup>
4 4 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
5 5 <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
6 6 <ProductVersion>8.0.30703</ProductVersion>
7 7 <SchemaVersion>2.0</SchemaVersion>
8 8 <ProjectGuid>{06E706F8-6881-43EB-927E-FFC503AF6ABC}</ProjectGuid>
9 9 <OutputType>Library</OutputType>
10 10 <AppDesignerFolder>Properties</AppDesignerFolder>
11 11 <RootNamespace>Implab.Fx</RootNamespace>
12 12 <AssemblyName>Implab.Fx</AssemblyName>
13 13 <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
14 14 <FileAlignment>512</FileAlignment>
15 15 <ReleaseVersion>0.2</ReleaseVersion>
16 16 <TargetFrameworkProfile />
17 17 </PropertyGroup>
18 18 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
19 19 <DebugSymbols>true</DebugSymbols>
20 20 <DebugType>full</DebugType>
21 21 <Optimize>false</Optimize>
22 22 <OutputPath>bin\Debug\</OutputPath>
23 23 <DefineConstants>DEBUG;TRACE</DefineConstants>
24 24 <ErrorReport>prompt</ErrorReport>
25 25 <WarningLevel>4</WarningLevel>
26 26 <Prefer32Bit>false</Prefer32Bit>
27 27 </PropertyGroup>
28 28 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
29 29 <DebugType>pdbonly</DebugType>
30 30 <Optimize>true</Optimize>
31 31 <OutputPath>bin\Release\</OutputPath>
32 32 <DefineConstants>TRACE</DefineConstants>
33 33 <ErrorReport>prompt</ErrorReport>
34 34 <WarningLevel>4</WarningLevel>
35 35 <Prefer32Bit>false</Prefer32Bit>
36 36 </PropertyGroup>
37 37 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug 4.5|AnyCPU' ">
38 38 <DebugSymbols>true</DebugSymbols>
39 39 <DebugType>full</DebugType>
40 40 <Optimize>false</Optimize>
41 41 <OutputPath>bin\Debug\</OutputPath>
42 42 <DefineConstants>DEBUG;TRACE</DefineConstants>
43 43 <ErrorReport>prompt</ErrorReport>
44 44 <WarningLevel>4</WarningLevel>
45 45 <Prefer32Bit>false</Prefer32Bit>
46 46 </PropertyGroup>
47 47 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release 4.5|AnyCPU' ">
48 48 <DebugType>pdbonly</DebugType>
49 49 <Optimize>true</Optimize>
50 50 <OutputPath>bin\Release\</OutputPath>
51 51 <DefineConstants>TRACE</DefineConstants>
52 52 <ErrorReport>prompt</ErrorReport>
53 53 <WarningLevel>4</WarningLevel>
54 54 <Prefer32Bit>false</Prefer32Bit>
55 55 </PropertyGroup>
56 56 <ItemGroup>
57 57 <Reference Include="System" />
58 58 <Reference Include="System.Core" />
59 59 <Reference Include="System.Drawing" />
60 60 <Reference Include="System.Windows.Forms" />
61 61 <Reference Include="System.Xml.Linq" />
62 62 <Reference Include="System.Data.DataSetExtensions" />
63 63 <Reference Include="Microsoft.CSharp" />
64 64 <Reference Include="System.Data" />
65 65 <Reference Include="System.Xml" />
66 66 </ItemGroup>
67 67 <ItemGroup>
68 68 <Compile Include="Animation.cs" />
69 69 <Compile Include="AnimationHelpers.cs" />
70 70 <Compile Include="PromiseHelpers.cs" />
71 71 <Compile Include="Properties\AssemblyInfo.cs" />
72 72 <Compile Include="ControlBoundPromise.cs" />
73 <Compile Include="StaApartment.cs" />
73 74 </ItemGroup>
74 75 <ItemGroup>
75 76 <ProjectReference Include="..\Implab\Implab.csproj">
76 77 <Project>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</Project>
77 78 <Name>Implab</Name>
78 79 </ProjectReference>
79 80 </ItemGroup>
80 81 <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
81 82 <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
82 83 Other similar extension points exist, see Microsoft.Common.targets.
83 84 <Target Name="BeforeBuild">
84 85 </Target>
85 86 <Target Name="AfterBuild">
86 87 </Target>
87 88 -->
88 89 </Project> No newline at end of file
@@ -1,84 +1,90
1 1 ο»Ώ<?xml version="1.0" encoding="utf-8"?>
2 2 <Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3 3 <PropertyGroup>
4 4 <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
5 5 <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
6 6 <ProductVersion>8.0.30703</ProductVersion>
7 7 <SchemaVersion>2.0</SchemaVersion>
8 8 <ProjectGuid>{63F92C0C-61BF-48C0-A377-8D67C3C661D0}</ProjectGuid>
9 9 <OutputType>Library</OutputType>
10 10 <AppDesignerFolder>Properties</AppDesignerFolder>
11 11 <RootNamespace>Implab.Test</RootNamespace>
12 12 <AssemblyName>Implab.Test</AssemblyName>
13 13 <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
14 14 <FileAlignment>512</FileAlignment>
15 15 <ProjectTypeGuids>{3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
16 16 <TargetFrameworkProfile />
17 17 </PropertyGroup>
18 18 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
19 19 <DebugSymbols>true</DebugSymbols>
20 20 <DebugType>full</DebugType>
21 21 <Optimize>false</Optimize>
22 22 <OutputPath>bin\Debug\</OutputPath>
23 23 <DefineConstants>DEBUG;TRACE</DefineConstants>
24 24 <ErrorReport>prompt</ErrorReport>
25 25 <WarningLevel>4</WarningLevel>
26 26 <Prefer32Bit>false</Prefer32Bit>
27 27 </PropertyGroup>
28 28 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
29 29 <DebugType>pdbonly</DebugType>
30 30 <Optimize>true</Optimize>
31 31 <OutputPath>bin\Release\</OutputPath>
32 32 <DefineConstants>TRACE</DefineConstants>
33 33 <ErrorReport>prompt</ErrorReport>
34 34 <WarningLevel>4</WarningLevel>
35 35 <Prefer32Bit>false</Prefer32Bit>
36 36 </PropertyGroup>
37 37 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug 4.5|AnyCPU' ">
38 38 <DebugSymbols>true</DebugSymbols>
39 39 <DebugType>full</DebugType>
40 40 <Optimize>false</Optimize>
41 41 <OutputPath>bin\Debug\</OutputPath>
42 42 <DefineConstants>DEBUG;TRACE</DefineConstants>
43 43 <ErrorReport>prompt</ErrorReport>
44 44 <WarningLevel>4</WarningLevel>
45 45 <Prefer32Bit>false</Prefer32Bit>
46 46 </PropertyGroup>
47 47 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release 4.5|AnyCPU' ">
48 48 <DebugType>pdbonly</DebugType>
49 49 <Optimize>true</Optimize>
50 50 <OutputPath>bin\Release\</OutputPath>
51 51 <DefineConstants>TRACE</DefineConstants>
52 52 <ErrorReport>prompt</ErrorReport>
53 53 <WarningLevel>4</WarningLevel>
54 54 <Prefer32Bit>false</Prefer32Bit>
55 55 </PropertyGroup>
56 56 <ItemGroup>
57 57 <Reference Include="Microsoft.VisualStudio.QualityTools.UnitTestFramework, Version=10.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL" />
58 58 <Reference Include="System" />
59 59 <Reference Include="System.Core">
60 60 <RequiredTargetFramework>3.5</RequiredTargetFramework>
61 61 </Reference>
62 62 </ItemGroup>
63 63 <ItemGroup>
64 64 <Compile Include="AsyncTests.cs" />
65 65 <Compile Include="CancelationTests.cs" />
66 <Compile Include="Mock\MockPollingComponent.cs" />
67 <Compile Include="Mock\MockRunnableComponent.cs" />
68 <Compile Include="PollingComponentTests.cs" />
66 69 <Compile Include="PromiseHelper.cs" />
67 70 <Compile Include="Properties\AssemblyInfo.cs" />
68 71 <Compile Include="RunnableComponentTests.cs" />
69 72 </ItemGroup>
70 73 <ItemGroup>
71 74 <ProjectReference Include="..\Implab\Implab.csproj">
72 75 <Project>{99B95D0D-9CF9-4F70-8ADF-F4D0AA5CB0D9}</Project>
73 76 <Name>Implab</Name>
74 77 </ProjectReference>
75 78 </ItemGroup>
79 <ItemGroup>
80 <Folder Include="Implab.Format.Test\" />
81 </ItemGroup>
76 82 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
77 83 <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
78 84 Other similar extension points exist, see Microsoft.Common.targets.
79 85 <Target Name="BeforeBuild">
80 86 </Target>
81 87 <Target Name="AfterBuild">
82 88 </Target>
83 89 -->
84 90 </Project> No newline at end of file
@@ -1,368 +1,411
1 1 using System;
2 2 using System.Diagnostics.CodeAnalysis;
3 3
4 4 namespace Implab.Components {
5 5 public abstract class RunnableComponent : IDisposable, IRunnable, IInitializable {
6 6 enum Commands {
7 7 Ok = 0,
8 8 Fail,
9 9 Init,
10 10 Start,
11 11 Stop,
12 12 Dispose,
13 13 Reset,
14 14 Last = Reset
15 15 }
16 16
17 17 class StateMachine {
18 static readonly ExecutionState[,] _transitions;
18 public static readonly ExecutionState[,] ReusableTransitions;
19 public static readonly ExecutionState[,] NonreusableTransitions;
20
21 class StateBuilder {
22 readonly ExecutionState[,] m_states;
23
24 public ExecutionState[,] States {
25 get { return m_states; }
26 }
27 public StateBuilder(ExecutionState[,] states) {
28 m_states = states;
29 }
30
31 public StateBuilder() {
32 m_states = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1];
33 }
34
35 public StateBuilder Edge(ExecutionState s1, ExecutionState s2, Commands cmd) {
36 m_states[(int)s1, (int)cmd] = s2;
37 return this;
38 }
39
40 public StateBuilder Clone() {
41 return new StateBuilder((ExecutionState[,])m_states.Clone());
42 }
43 }
19 44
20 45 static StateMachine() {
21 _transitions = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1];
46 ReusableTransitions = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1];
22 47
23 Edge(ExecutionState.Created, ExecutionState.Initializing, Commands.Init);
24 Edge(ExecutionState.Created, ExecutionState.Disposed, Commands.Dispose);
48 var common = new StateBuilder()
49 .Edge(ExecutionState.Created, ExecutionState.Initializing, Commands.Init)
50 .Edge(ExecutionState.Created, ExecutionState.Disposed, Commands.Dispose)
25 51
26 Edge(ExecutionState.Initializing, ExecutionState.Ready, Commands.Ok);
27 Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail);
52 .Edge(ExecutionState.Initializing, ExecutionState.Ready, Commands.Ok)
53 .Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail)
28 54
29 Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start);
30 Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose);
55 .Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start)
56 .Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose)
57
58 .Edge(ExecutionState.Starting, ExecutionState.Running, Commands.Ok)
59 .Edge(ExecutionState.Starting, ExecutionState.Failed, Commands.Fail)
60 .Edge(ExecutionState.Starting, ExecutionState.Stopping, Commands.Stop)
61 .Edge(ExecutionState.Starting, ExecutionState.Disposed, Commands.Dispose)
31 62
32 Edge(ExecutionState.Starting, ExecutionState.Running, Commands.Ok);
33 Edge(ExecutionState.Starting, ExecutionState.Failed, Commands.Fail);
34 Edge(ExecutionState.Starting, ExecutionState.Stopping, Commands.Stop);
35 Edge(ExecutionState.Starting, ExecutionState.Disposed, Commands.Dispose);
63 .Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail)
64 .Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop)
65 .Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose)
66
67 .Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose)
68 .Edge(ExecutionState.Failed, ExecutionState.Initializing, Commands.Reset)
69
70 .Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail)
71 .Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose)
72
73 .Edge(ExecutionState.Disposed, ExecutionState.Disposed, Commands.Dispose);
36 74
37 Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail);
38 Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop);
39 Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose);
75 var reusable = common
76 .Clone()
77 .Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok);
40 78
41 Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail);
42 Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok);
43 Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose);
79 var nonreusable = common
80 .Clone()
81 .Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok);
44 82
45 Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose);
46 Edge(ExecutionState.Failed, ExecutionState.Initializing, Commands.Reset);
83 NonreusableTransitions = nonreusable.States;
84 ReusableTransitions = reusable.States;
85
47 86 }
48 87
49 static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) {
50 _transitions[(int)s1, (int)cmd] = s2;
51 }
88 readonly ExecutionState[,] m_states;
52 89
53 90 public ExecutionState State {
54 91 get;
55 92 private set;
56 93 }
57 94
58 public StateMachine(ExecutionState initial) {
95 public StateMachine(ExecutionState[,] states, ExecutionState initial) {
59 96 State = initial;
97 m_states = states;
60 98 }
61 99
62 100 public bool Move(Commands cmd) {
63 var next = _transitions[(int)State, (int)cmd];
101 var next = m_states[(int)State, (int)cmd];
64 102 if (next == ExecutionState.Undefined)
65 103 return false;
66 104 State = next;
67 105 return true;
68 106 }
69 107 }
70 108
71 109 IPromise m_pending;
72 110 Exception m_lastError;
73 111
74 112 readonly StateMachine m_stateMachine;
75 113 readonly bool m_reusable;
76 114 public event EventHandler<StateChangeEventArgs> StateChanged;
77 115
78 116 /// <summary>
79 117 /// Initializes component state.
80 118 /// </summary>
81 119 /// <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>
82 120 /// <param name="reusable">If set, the component may start after it has been stopped, otherwise the component is disposed after being stopped.</param>
83 121 protected RunnableComponent(bool initialized, bool reusable) {
84 m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created);
122 m_stateMachine = new StateMachine(
123 reusable ? StateMachine.ReusableTransitions : StateMachine.NonreusableTransitions,
124 initialized ? ExecutionState.Ready : ExecutionState.Created
125 );
85 126 m_reusable = reusable;
86 DisposeTimeout = 10000;
87 127 }
88 128
89 129 /// <summary>
90 130 /// Initializes component state. The component created with this constructor is not reusable, i.e. it will be disposed after stop.
91 131 /// </summary>
92 132 /// <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>
93 133 protected RunnableComponent(bool initialized) : this(initialized, false) {
94 134 }
95 135
96 /// <summary>
97 /// Gets or sets the timeout to wait for the pending operation to complete. If the pending operation doesn't finish than the component will be disposed anyway.
98 /// </summary>
99 protected int DisposeTimeout {
100 get;
101 set;
102 }
103
104 136 void ThrowInvalidCommand(Commands cmd) {
105 137 if (m_stateMachine.State == ExecutionState.Disposed)
106 138 throw new ObjectDisposedException(ToString());
107 139
108 140 throw new InvalidOperationException(String.Format("Command {0} is not allowed in the state {1}", cmd, m_stateMachine.State));
109 141 }
110 142
111 143 bool MoveIfInState(Commands cmd, IPromise pending, Exception error, ExecutionState state) {
112 144 ExecutionState prev, current;
113 145 lock (m_stateMachine) {
114 146 if (m_stateMachine.State != state)
115 147 return false;
116 148
117 149 prev = m_stateMachine.State;
118 150 if (!m_stateMachine.Move(cmd))
119 151 ThrowInvalidCommand(cmd);
120 152 current = m_stateMachine.State;
121 153
122 154 m_pending = pending;
123 155 m_lastError = error;
124 156 }
125 157 if (prev != current)
126 158 OnStateChanged(prev, current, error);
127 159 return true;
128 160 }
129 161
130 162 bool MoveIfPending(Commands cmd, IPromise pending, Exception error, IPromise expected) {
131 163 ExecutionState prev, current;
132 164 lock (m_stateMachine) {
133 165 if (m_pending != expected)
134 166 return false;
135 167 prev = m_stateMachine.State;
136 168 if (!m_stateMachine.Move(cmd))
137 169 ThrowInvalidCommand(cmd);
138 170 current = m_stateMachine.State;
139 171 m_pending = pending;
140 172 m_lastError = error;
141 173 }
142 174 if (prev != current)
143 175 OnStateChanged(prev, current, error);
144 176 return true;
145 177 }
146 178
147 179 IPromise Move(Commands cmd, IPromise pending, Exception error) {
148 180 ExecutionState prev, current;
149 181 IPromise ret;
150 182 lock (m_stateMachine) {
151 183 prev = m_stateMachine.State;
152 184 if (!m_stateMachine.Move(cmd))
153 185 ThrowInvalidCommand(cmd);
154 186 current = m_stateMachine.State;
155 187
156 188 ret = m_pending;
157 189 m_pending = pending;
158 190 m_lastError = error;
159 191
160 192 }
161 193 if(prev != current)
162 194 OnStateChanged(prev, current, error);
163 195 return ret;
164 196 }
165 197
198 /// <summary>
199 /// Handles the state of the component change event, raises the <see cref="StateChanged"/> event, handles
200 /// the transition to the <see cref="ExecutionState.Disposed"/> state (calls <see cref="Dispose(bool)"/> method).
201 /// </summary>
202 /// <param name="previous">The previous state</param>
203 /// <param name="current">The current state</param>
204 /// <param name="error">The last error if any.</param>
205 /// <remarks>
206 /// <para>
207 /// If the previous state and the current state are same this method isn't called, such situiation is treated
208 /// as the component hasn't changed it's state.
209 /// </para>
210 /// <para>
211 /// When overriding this method ensure the call is made to the base implementation, otherwise it will lead to
212 /// the wrong behavior of the component.
213 /// </para>
214 /// </remarks>
166 215 protected virtual void OnStateChanged(ExecutionState previous, ExecutionState current, Exception error) {
167 var h = StateChanged;
168 if (h != null)
169 h(this, new StateChangeEventArgs {
216 StateChanged.DispatchEvent(
217 this,
218 new StateChangeEventArgs {
170 219 State = current,
171 220 LastError = error
172 });
221 }
222 );
223 if (current == ExecutionState.Disposed) {
224 GC.SuppressFinalize(this);
225 Dispose(true);
226 }
173 227 }
174 228
175 229 /// <summary>
176 230 /// Moves the component from running to failed state.
177 231 /// </summary>
178 232 /// <param name="error">The exception which is describing the error.</param>
179 233 protected bool Fail(Exception error) {
180 234 return MoveIfInState(Commands.Fail, null, error, ExecutionState.Running);
181 235 }
182 236
183 237 /// <summary>
184 238 /// Tries to reset <see cref="ExecutionState.Failed"/> state to <see cref="ExecutionState.Ready"/>.
185 239 /// </summary>
186 240 /// <returns>True if component is reset to <see cref="ExecutionState.Ready"/>, false if the componet wasn't
187 241 /// in <see cref="ExecutionState.Failed"/> state.</returns>
188 242 /// <remarks>
189 243 /// This method checks the current state of the component and if it's in <see cref="ExecutionState.Failed"/>
190 244 /// moves component to <see cref="ExecutionState.Initializing"/>.
191 245 /// The <see cref="OnResetState()"/> is called and if this method completes succesfully the component moved
192 246 /// to <see cref="ExecutionState.Ready"/> state, otherwise the component is moved to <see cref="ExecutionState.Failed"/>
193 247 /// state. If <see cref="OnResetState()"/> throws an exception it will be propagated by this method to the caller.
194 248 /// </remarks>
195 249 protected bool ResetState() {
196 250 if (!MoveIfInState(Commands.Reset, null, null, ExecutionState.Failed))
197 251 return false;
198 252
199 253 try {
200 254 OnResetState();
201 255 Move(Commands.Ok, null, null);
202 256 return true;
203 257 } catch (Exception err) {
204 258 Move(Commands.Fail, null, err);
205 259 throw;
206 260 }
207 261 }
208 262
209 263 /// <summary>
210 264 /// This method is called by <see cref="ResetState"/> to reinitialize component in the failed state.
211 265 /// </summary>
212 266 /// <remarks>
213 267 /// Default implementation throws <see cref="NotImplementedException"/> which will cause the component
214 268 /// fail to reset it's state and it left in <see cref="ExecutionState.Failed"/> state.
215 269 /// If this method doesn't throw exceptions the component is moved to <see cref="ExecutionState.Ready"/> state.
216 270 /// </remarks>
217 271 protected virtual void OnResetState() {
218 272 throw new NotImplementedException();
219 273 }
220 274
221 275 IPromise InvokeAsync(Commands cmd, Func<IPromise> action, Action<IPromise, IDeferred> chain) {
222 276 IPromise promise = null;
223 277 IPromise prev;
224 278
225 279 var task = new ActionChainTask(action, null, null, true);
226 280
227 281 Action<Exception> errorOrCancel = e => {
228 282 if (e == null)
229 283 e = new OperationCanceledException();
230 284 MoveIfPending(Commands.Fail, null, e, promise);
231 285 throw new PromiseTransientException(e);
232 286 };
233 287
234 288 promise = task.Then(
235 289 () => MoveIfPending(Commands.Ok, null, null, promise),
236 290 errorOrCancel,
237 291 errorOrCancel
238 292 );
239 293
240 294 prev = Move(cmd, promise, null);
241 295
242 296 if (prev == null)
243 297 task.Resolve();
244 298 else
245 299 chain(prev, task);
246 300
247 301 return promise;
248 302 }
249 303
250 304
251 305 #region IInitializable implementation
252 306
253 307 public void Initialize() {
254 308 Move(Commands.Init, null, null);
255 309
256 310 try {
257 311 OnInitialize();
258 312 Move(Commands.Ok, null, null);
259 313 } catch (Exception err) {
260 314 Move(Commands.Fail, null, err);
261 315 throw;
262 316 }
263 317 }
264 318
265 319 protected virtual void OnInitialize() {
266 320 }
267 321
268 322 #endregion
269 323
270 324 #region IRunnable implementation
271 325
272 326 public IPromise Start() {
273 327 return InvokeAsync(Commands.Start, OnStart, null);
274 328 }
275 329
276 330 protected virtual IPromise OnStart() {
277 331 return Promise.Success;
278 332 }
279 333
280 334 public IPromise Stop() {
281 var pending = InvokeAsync(Commands.Stop, OnStop, StopPending);
282 return m_reusable ? pending : pending.Then(Dispose);
335 return InvokeAsync(Commands.Stop, OnStop, StopPending);
283 336 }
284 337
285 338 protected virtual IPromise OnStop() {
286 339 return Promise.Success;
287 340 }
288 341
289 342 /// <summary>
290 343 /// Stops the current operation if one exists.
291 344 /// </summary>
292 345 /// <param name="current">Current.</param>
293 346 /// <param name="stop">Stop.</param>
294 347 protected virtual void StopPending(IPromise current, IDeferred stop) {
295 348 if (current == null) {
296 349 stop.Resolve();
297 350 } else {
298 351 // связваСм Ρ‚Π΅ΠΊΡƒΡ‰ΡƒΡŽ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΡŽ с ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠ΅ΠΉ остановки
299 352 current.On(
300 353 stop.Resolve, // Ссли тСкущая опСрация Π·Π°Π²Π΅Ρ€Ρ‰ΠΈΠ»Π°ΡΡŒ, Ρ‚ΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°Ρ‡ΠΈΠ½Π°Ρ‚ΡŒ остановку
301 354 stop.Reject, // Ссли тСкущая опСрация Π΄Π°Π»Π° ΠΎΡˆΠΈΠ±ΠΊΡƒ - Ρ‚ΠΎ всС ΠΏΠ»ΠΎΡ…ΠΎ, нСльзя ΠΏΡ€ΠΎΠ΄ΠΎΠ»ΠΆΠ°Ρ‚ΡŒ
302 355 e => stop.Resolve() // Ссли тСкущая ΠΎΡ‚ΠΌΠ΅Π½ΠΈΠ»Π°ΡΡŒ, Ρ‚ΠΎ ΠΌΠΎΠΆΠ½ΠΎ Π½Π°Ρ‡ΠΈΠ½Π°Ρ‚ΡŒ остановку
303 356 );
304 357 // посылаСм Ρ‚Π΅ΠΊΡƒΡ‰Π΅ΠΉ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ сигнал остановки
305 358 current.Cancel();
306 359 }
307 360 }
308 361
309 362 public ExecutionState State {
310 363 get {
311 364 return m_stateMachine.State;
312 365 }
313 366 }
314 367
315 368 public Exception LastError {
316 369 get {
317 370 return m_lastError;
318 371 }
319 372 }
320 373
321 374 #endregion
322 375
323 376 #region IDisposable implementation
324 377
325 378 /// <summary>
326 379 /// Releases all resource used by the <see cref="Implab.Components.RunnableComponent"/> object.
327 380 /// </summary>
328 381 /// <remarks>
329 382 /// <para>Will not try to stop the component, it will just release all resources.
330 383 /// To cleanup the component gracefully use <see cref="Stop()"/> method.</para>
331 384 /// <para>
332 385 /// In normal cases the <see cref="Dispose()"/> method shouldn't be called, the call to the <see cref="Stop()"/>
333 386 /// method is sufficient to cleanup the component. Call <see cref="Dispose()"/> only to cleanup after errors,
334 387 /// especially if <see cref="Stop"/> method is failed. Using this method insted of <see cref="Stop()"/> may
335 388 /// lead to the data loss by the component.
336 389 /// </para></remarks>
337 390 [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Dipose(bool) and GC.SuppessFinalize are called")]
338 391 public void Dispose() {
339 IPromise pending;
340
341 lock (m_stateMachine) {
342 if (m_stateMachine.State == ExecutionState.Disposed)
343 return;
344 392 Move(Commands.Dispose, null, null);
345 393 }
346 394
347 GC.SuppressFinalize(this);
348 Dispose(true);
349 }
350
351 395 ~RunnableComponent() {
352 396 Dispose(false);
353 397 }
354 398
355 399 #endregion
356 400
357 401 /// <summary>
358 402 /// Releases all resources used by the component, called automatically, override this method to implement your cleanup.
359 403 /// </summary>
360 404 /// <param name="disposing">true if this method is called during normal dispose process.</param>
361 405 /// <param name="pending">The operation which is currenty pending</param>
362 406 protected virtual void Dispose(bool disposing) {
363
364 407 }
365 408
366 409 }
367 410 }
368 411
General Comments 3
Under Review
author

Auto status change to "Under Review"

Approved
author

ok, latest stable version should be in default

You need to be logged in to leave comments. Login now