##// 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 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">
@@ -70,6 +70,7
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">
@@ -63,6 +63,9
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" />
@@ -73,6 +76,9
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.
@@ -15,52 +15,90 namespace Implab.Components {
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);
78
79 var nonreusable = common
80 .Clone()
81 .Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok);
82
83 NonreusableTransitions = nonreusable.States;
84 ReusableTransitions = reusable.States;
40 85
41 Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail);
42 Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok);
43 Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose);
44
45 Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose);
46 Edge(ExecutionState.Failed, ExecutionState.Initializing, Commands.Reset);
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;
@@ -81,9 +119,11 namespace Implab.Components {
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>
@@ -93,14 +133,6 namespace Implab.Components {
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());
@@ -155,21 +187,43 namespace Implab.Components {
155 187
156 188 ret = m_pending;
157 189 m_pending = pending;
158 m_lastError = error;
159
190 m_lastError = error;
191
160 192 }
161 if(prev != current)
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>
@@ -278,8 +332,7 namespace Implab.Components {
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() {
@@ -336,16 +389,7 namespace Implab.Components {
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 Move(Commands.Dispose, null, null);
345 }
346
347 GC.SuppressFinalize(this);
348 Dispose(true);
392 Move(Commands.Dispose, null, null);
349 393 }
350 394
351 395 ~RunnableComponent() {
@@ -360,7 +404,6 namespace Implab.Components {
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 }
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