##// 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 }
@@ -80,6 +80,7
80 <Compile Include="Sample\OverlayForm.Designer.cs">
80 <Compile Include="Sample\OverlayForm.Designer.cs">
81 <DependentUpon>OverlayForm.cs</DependentUpon>
81 <DependentUpon>OverlayForm.cs</DependentUpon>
82 </Compile>
82 </Compile>
83 <Compile Include="StaApartmentTests.cs" />
83 </ItemGroup>
84 </ItemGroup>
84 <ItemGroup>
85 <ItemGroup>
85 <EmbeddedResource Include="Sample\MainForm.resx">
86 <EmbeddedResource Include="Sample\MainForm.resx">
@@ -70,6 +70,7
70 <Compile Include="PromiseHelpers.cs" />
70 <Compile Include="PromiseHelpers.cs" />
71 <Compile Include="Properties\AssemblyInfo.cs" />
71 <Compile Include="Properties\AssemblyInfo.cs" />
72 <Compile Include="ControlBoundPromise.cs" />
72 <Compile Include="ControlBoundPromise.cs" />
73 <Compile Include="StaApartment.cs" />
73 </ItemGroup>
74 </ItemGroup>
74 <ItemGroup>
75 <ItemGroup>
75 <ProjectReference Include="..\Implab\Implab.csproj">
76 <ProjectReference Include="..\Implab\Implab.csproj">
@@ -63,6 +63,9
63 <ItemGroup>
63 <ItemGroup>
64 <Compile Include="AsyncTests.cs" />
64 <Compile Include="AsyncTests.cs" />
65 <Compile Include="CancelationTests.cs" />
65 <Compile Include="CancelationTests.cs" />
66 <Compile Include="Mock\MockPollingComponent.cs" />
67 <Compile Include="Mock\MockRunnableComponent.cs" />
68 <Compile Include="PollingComponentTests.cs" />
66 <Compile Include="PromiseHelper.cs" />
69 <Compile Include="PromiseHelper.cs" />
67 <Compile Include="Properties\AssemblyInfo.cs" />
70 <Compile Include="Properties\AssemblyInfo.cs" />
68 <Compile Include="RunnableComponentTests.cs" />
71 <Compile Include="RunnableComponentTests.cs" />
@@ -73,6 +76,9
73 <Name>Implab</Name>
76 <Name>Implab</Name>
74 </ProjectReference>
77 </ProjectReference>
75 </ItemGroup>
78 </ItemGroup>
79 <ItemGroup>
80 <Folder Include="Implab.Format.Test\" />
81 </ItemGroup>
76 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
82 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
77 <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
83 <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
78 Other similar extension points exist, see Microsoft.Common.targets.
84 Other similar extension points exist, see Microsoft.Common.targets.
@@ -15,52 +15,90 namespace Implab.Components {
15 }
15 }
16
16
17 class StateMachine {
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 static StateMachine() {
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);
48 var common = new StateBuilder()
24 Edge(ExecutionState.Created, ExecutionState.Disposed, Commands.Dispose);
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);
52 .Edge(ExecutionState.Initializing, ExecutionState.Ready, Commands.Ok)
27 Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail);
53 .Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail)
28
54
29 Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start);
55 .Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start)
30 Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose);
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);
63 .Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail)
33 Edge(ExecutionState.Starting, ExecutionState.Failed, Commands.Fail);
64 .Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop)
34 Edge(ExecutionState.Starting, ExecutionState.Stopping, Commands.Stop);
65 .Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose)
35 Edge(ExecutionState.Starting, 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);
75 var reusable = common
38 Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop);
76 .Clone()
39 Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose);
77 .Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok);
40
78
41 Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail);
79 var nonreusable = common
42 Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok);
80 .Clone()
43 Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose);
81 .Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok);
44
82
45 Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose);
83 NonreusableTransitions = nonreusable.States;
46 Edge(ExecutionState.Failed, ExecutionState.Initializing, Commands.Reset);
84 ReusableTransitions = reusable.States;
85
47 }
86 }
48
87
49 static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) {
88 readonly ExecutionState[,] m_states;
50 _transitions[(int)s1, (int)cmd] = s2;
51 }
52
89
53 public ExecutionState State {
90 public ExecutionState State {
54 get;
91 get;
55 private set;
92 private set;
56 }
93 }
57
94
58 public StateMachine(ExecutionState initial) {
95 public StateMachine(ExecutionState[,] states, ExecutionState initial) {
59 State = initial;
96 State = initial;
97 m_states = states;
60 }
98 }
61
99
62 public bool Move(Commands cmd) {
100 public bool Move(Commands cmd) {
63 var next = _transitions[(int)State, (int)cmd];
101 var next = m_states[(int)State, (int)cmd];
64 if (next == ExecutionState.Undefined)
102 if (next == ExecutionState.Undefined)
65 return false;
103 return false;
66 State = next;
104 State = next;
@@ -81,9 +119,11 namespace Implab.Components {
81 /// <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>
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 /// <param name="reusable">If set, the component may start after it has been stopped, otherwise the component is disposed after being stopped.</param>
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 protected RunnableComponent(bool initialized, bool reusable) {
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 m_reusable = reusable;
126 m_reusable = reusable;
86 DisposeTimeout = 10000;
87 }
127 }
88
128
89 /// <summary>
129 /// <summary>
@@ -93,14 +133,6 namespace Implab.Components {
93 protected RunnableComponent(bool initialized) : this(initialized, false) {
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 void ThrowInvalidCommand(Commands cmd) {
136 void ThrowInvalidCommand(Commands cmd) {
105 if (m_stateMachine.State == ExecutionState.Disposed)
137 if (m_stateMachine.State == ExecutionState.Disposed)
106 throw new ObjectDisposedException(ToString());
138 throw new ObjectDisposedException(ToString());
@@ -163,13 +195,35 namespace Implab.Components {
163 return ret;
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 protected virtual void OnStateChanged(ExecutionState previous, ExecutionState current, Exception error) {
215 protected virtual void OnStateChanged(ExecutionState previous, ExecutionState current, Exception error) {
167 var h = StateChanged;
216 StateChanged.DispatchEvent(
168 if (h != null)
217 this,
169 h(this, new StateChangeEventArgs {
218 new StateChangeEventArgs {
170 State = current,
219 State = current,
171 LastError = error
220 LastError = error
172 });
221 }
222 );
223 if (current == ExecutionState.Disposed) {
224 GC.SuppressFinalize(this);
225 Dispose(true);
226 }
173 }
227 }
174
228
175 /// <summary>
229 /// <summary>
@@ -278,8 +332,7 namespace Implab.Components {
278 }
332 }
279
333
280 public IPromise Stop() {
334 public IPromise Stop() {
281 var pending = InvokeAsync(Commands.Stop, OnStop, StopPending);
335 return InvokeAsync(Commands.Stop, OnStop, StopPending);
282 return m_reusable ? pending : pending.Then(Dispose);
283 }
336 }
284
337
285 protected virtual IPromise OnStop() {
338 protected virtual IPromise OnStop() {
@@ -336,18 +389,9 namespace Implab.Components {
336 /// </para></remarks>
389 /// </para></remarks>
337 [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Dipose(bool) and GC.SuppessFinalize are called")]
390 [SuppressMessage("Microsoft.Design", "CA1063:ImplementIDisposableCorrectly", Justification = "Dipose(bool) and GC.SuppessFinalize are called")]
338 public void Dispose() {
391 public void Dispose() {
339 IPromise pending;
340
341 lock (m_stateMachine) {
342 if (m_stateMachine.State == ExecutionState.Disposed)
343 return;
344 Move(Commands.Dispose, null, null);
392 Move(Commands.Dispose, null, null);
345 }
393 }
346
394
347 GC.SuppressFinalize(this);
348 Dispose(true);
349 }
350
351 ~RunnableComponent() {
395 ~RunnableComponent() {
352 Dispose(false);
396 Dispose(false);
353 }
397 }
@@ -360,7 +404,6 namespace Implab.Components {
360 /// <param name="disposing">true if this method is called during normal dispose process.</param>
404 /// <param name="disposing">true if this method is called during normal dispose process.</param>
361 /// <param name="pending">The operation which is currenty pending</param>
405 /// <param name="pending">The operation which is currenty pending</param>
362 protected virtual void Dispose(bool disposing) {
406 protected virtual void Dispose(bool disposing) {
363
364 }
407 }
365
408
366 }
409 }
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