##// END OF EJS Templates
inital progress handling
cin -
r7:7ea9363fef6c promises
parent child
Show More
@@ -0,0 +1,33
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace Implab
7 {
8 public interface IPromise
9 {
10 /// <summary>
11 /// Check whereather the promise has no more than one dependent promise.
12 /// </summary>
13 bool IsExclusive
14 {
15 get;
16 }
17
18 /// <summary>
19 /// The current state of the promise.
20 /// </summary>
21 PromiseState State
22 {
23 get;
24 }
25
26 /// <summary>
27 /// Tries to cancel the promise or the complete chain.
28 /// </summary>
29 /// <param name="dependencies">Try to cancel the parent promise is it has the only one child</param>
30 /// <returns></returns>
31 bool Cancel(bool dependencies);
32 }
33 }
@@ -0,0 +1,36
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace Implab
7 {
8
9 public class ProgressInitEventArgs: EventArgs
10 {
11 public float MaxProgress
12 {
13 get;
14 private set;
15 }
16
17 public float CurrentProgress
18 {
19 get;
20 private set;
21 }
22
23 public string Message
24 {
25 get;
26 private set;
27 }
28
29 public ProgressInitEventArgs(float current, float max, string message)
30 {
31 this.MaxProgress = max;
32 this.CurrentProgress = current;
33 this.Message = message;
34 }
35 }
36 }
@@ -0,0 +1,15
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace Implab
7 {
8 public enum PromiseState
9 {
10 Unresolved,
11 Resolved,
12 Cancelled,
13 Rejected
14 }
15 }
@@ -0,0 +1,113
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Threading;
6
7 namespace Implab
8 {
9 /// <summary>
10 /// This class allows to interact with asyncronuos task.
11 /// </summary>
12 /// <remarks>
13 /// Members of this object are thread safe.
14 /// </remarks>
15 class TaskController
16 {
17 object m_lock;
18 string m_message;
19 bool m_cancelled;
20
21 float m_current;
22 float m_max;
23
24 public event EventHandler<ValueEventArgs<string>> MessageUpdated;
25 public event EventHandler<ValueEventArgs<float>> ProgressUpdated;
26 public event EventHandler<ProgressInitEventArgs> ProgressInit;
27
28 public TaskController()
29 {
30 m_lock = new Object();
31 }
32
33 public string Message
34 {
35 get
36 {
37 lock (m_lock)
38 return m_message;
39 }
40 set
41 {
42 lock (m_lock)
43 {
44 m_message = value;
45 OnMessageUpdated();
46 }
47 }
48 }
49
50 public float CurrentProgress
51 {
52 get
53 {
54 lock (m_lock)
55 return m_current;
56 }
57 set
58 {
59 lock (m_lock)
60 {
61 var prev = m_current;
62 m_current = value;
63 if (m_current >= m_max)
64 m_current = m_max;
65 if (m_current != prev)
66 OnProgressUpdated();
67 }
68 }
69 }
70
71 public void InitProgress(float current, float max, string message)
72 {
73 if (max < 0)
74 throw new ArgumentOutOfRangeException("max");
75 if (current < 0 || current > max)
76 throw new ArgumentOutOfRangeException("current");
77
78 lock(m_lock) {
79 m_current = current;
80 m_max = max;
81 m_message = message;
82 OnProgressInit();
83 }
84 }
85
86 protected virtual void OnMessageUpdated()
87 {
88 var temp = MessageUpdated;
89 if (temp != null)
90 {
91 temp(this, new ValueEventArgs<string>(m_message));
92 }
93 }
94
95 protected virtual void OnProgressUpdated()
96 {
97 var temp = ProgressUpdated;
98 if (temp != null)
99 {
100 temp(this,new ValueEventArgs<float>(m_current));
101 }
102 }
103
104 protected virtual void OnProgressInit()
105 {
106 var temp = ProgressInit;
107 if (temp != null)
108 {
109 temp(this, new ProgressInitEventArgs(m_current,m_max, m_message));
110 }
111 }
112 }
113 }
@@ -0,0 +1,20
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5
6 namespace Implab
7 {
8 public class ValueEventArgs<T>: EventArgs
9 {
10 public ValueEventArgs(T value)
11 {
12 this.Value = value;
13 }
14 public T Value
15 {
16 get;
17 private set;
18 }
19 }
20 }
1 NO CONTENT: modified file, binary diff hidden
@@ -1,44 +1,48
1 1 <?xml version="1.0" encoding="utf-8"?>
2 2 <Project DefaultTargets="Build" ToolsVersion="4.0" 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>10.0.0</ProductVersion>
7 7 <SchemaVersion>2.0</SchemaVersion>
8 8 <ProjectGuid>{F550F1F8-8746-4AD0-9614-855F4C4B7F05}</ProjectGuid>
9 9 <OutputType>Library</OutputType>
10 10 <RootNamespace>Implab</RootNamespace>
11 11 <AssemblyName>Implab</AssemblyName>
12 12 </PropertyGroup>
13 13 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
14 14 <DebugSymbols>true</DebugSymbols>
15 15 <DebugType>full</DebugType>
16 16 <Optimize>false</Optimize>
17 17 <OutputPath>bin\Debug</OutputPath>
18 18 <DefineConstants>DEBUG;</DefineConstants>
19 19 <ErrorReport>prompt</ErrorReport>
20 20 <WarningLevel>4</WarningLevel>
21 21 <ConsolePause>false</ConsolePause>
22 22 </PropertyGroup>
23 23 <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
24 24 <DebugType>full</DebugType>
25 25 <Optimize>true</Optimize>
26 26 <OutputPath>bin\Release</OutputPath>
27 27 <ErrorReport>prompt</ErrorReport>
28 28 <WarningLevel>4</WarningLevel>
29 29 <ConsolePause>false</ConsolePause>
30 30 </PropertyGroup>
31 31 <ItemGroup>
32 32 <Reference Include="System" />
33 33 </ItemGroup>
34 34 <ItemGroup>
35 <Compile Include="ICancellable.cs" />
36 <Compile Include="TaskController.cs" />
37 <Compile Include="ProgressInitEventArgs.cs" />
35 38 <Compile Include="Properties\AssemblyInfo.cs" />
36 39 <Compile Include="Promise.cs" />
37 40 <Compile Include="AsyncPool.cs" />
38 41 <Compile Include="Safe.cs" />
42 <Compile Include="ValueEventArgs.cs" />
39 43 </ItemGroup>
40 44 <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
41 45 <ItemGroup>
42 46 <Folder Include="Parallels\" />
43 47 </ItemGroup>
44 48 </Project> No newline at end of file
@@ -1,397 +1,399
1 1 using System;
2 2 using System.Collections.Generic;
3 3 using System.Linq;
4 4 using System.Reflection;
5 5 using System.Text;
6 6 using System.Diagnostics;
7 7 using System.Threading;
8 8
9 9 namespace Implab {
10 10
11 11 public delegate void ErrorHandler(Exception e);
12 12
13 13 public delegate void ResultHandler<T>(T result);
14 14 public delegate TNew ResultMapper<TSrc, TNew>(TSrc result);
15 15 public delegate Promise<TNew> ChainedOperation<TSrc, TNew>(TSrc result);
16 16
17 17 /// <summary>
18 18 /// Класс для асинхронного получения результатов. Так называемое "обещание".
19 19 /// </summary>
20 20 /// <typeparam name="T">Тип получаемого результата</typeparam>
21 21 /// <remarks>
22 22 /// <para>Сервис при обращении к его методу дает обещаиние о выполнении операции,
23 23 /// клиент получив такое обещание может установить ряд обратных вызово для получения
24 24 /// событий выполнения обещания, тоесть завершения операции и предоставлении результатов.</para>
25 25 /// <para>
26 26 /// Обещение может быть как выполнено, так и выполнено с ошибкой. Для подписки на
27 27 /// данные события клиент должен использовать методы <c>Then</c>.
28 28 /// </para>
29 29 /// <para>
30 30 /// Сервис, в свою очередь, по окончанию выполнения операции (возможно с ошибкой),
31 31 /// использует методы <c>Resolve</c> либо <c>Reject</c> для оповещения клиетна о
32 32 /// выполнении обещания.
33 33 /// </para>
34 34 /// <para>
35 35 /// Если сервер успел выполнить обещание еще до того, как клиент на него подписался,
36 36 /// то в момент подписки клиента будут вызваны соответсвующие события в синхронном
37 37 /// режиме и клиент будет оповещен в любом случае. Иначе, обработчики добавляются в
38 38 /// список в порядке подписания и в этом же порядке они будут вызваны при выполнении
39 39 /// обещания.
40 40 /// </para>
41 41 /// <para>
42 42 /// Обрабатывая результаты обещания можно преобразовывать результаты либо инициировать
43 43 /// связанные асинхронные операции, которые также возвращают обещания. Для этого следует
44 44 /// использовать соответствующую форму методе <c>Then</c>.
45 45 /// </para>
46 46 /// <para>
47 47 /// Также хорошим правилом является то, что <c>Resolve</c> и <c>Reject</c> должен вызывать
48 48 /// только инициатор обещания иначе могут возникнуть противоречия.
49 49 /// </para>
50 50 /// </remarks>
51 public class Promise<T> {
51 public class Promise<T>: IPromise {
52 52
53 53 struct ResultHandlerInfo {
54 54 public ResultHandler<T> resultHandler;
55 55 public ErrorHandler errorHandler;
56 }
57
58 enum State {
59 Unresolved,
60 Resolving,
61 Resolved,
62 Cancelled
56 public Action cancelHandler;
63 57 }
64 58
65 59 LinkedList<ResultHandlerInfo> m_handlersChain = new LinkedList<ResultHandlerInfo>();
66 State m_state;
60 PromiseState m_state;
67 61 bool m_cancellable;
68 62 T m_result;
69 63 Exception m_error;
64 IPromise m_parent;
65 int m_childrenCount;
70 66
71 67 public Promise() {
72 68 m_cancellable = true;
73 69 }
74 70
71 public Promise(IPromise parent, bool cancellable)
72 {
73 m_cancellable = cancellable;
74 m_parent = parent;
75 }
76
75 77 /// <summary>
76 78 /// Событие, возникающее при отмене асинхронной операции.
77 79 /// </summary>
78 80 /// <description>
79 81 /// Как правило используется для оповещения объекта, выполняющего асинхронную операцию, о том, что ее следует отменить.
80 82 /// </description>
81 83 public event EventHandler Cancelled;
82 84
83 85 /// <summary>
84 86 /// Выполняет обещание, сообщая об успешном выполнении.
85 87 /// </summary>
86 88 /// <param name="result">Результат выполнения.</param>
87 89 /// <exception cref="InvalidOperationException">Данное обещание уже выполнено</exception>
88 90 public void Resolve(T result) {
89 91 lock (this) {
90 if (m_state == State.Cancelled)
92 if (m_state == PromiseState.Cancelled)
91 93 return;
92 if (m_state != State.Unresolved)
94 if (m_state != PromiseState.Unresolved)
93 95 throw new InvalidOperationException("The promise is already resolved");
94 96 m_result = result;
95 m_state = State.Resolving;
97 m_state = PromiseState.Resolved;
96 98 }
97 99
98 100 ResultHandlerInfo handler;
99 101 while (FetchNextHandler(out handler))
100 102 InvokeHandler(handler);
101 103 }
102 104
103 105 /// <summary>
104 106 /// Выполняет обещание, сообщая об ошибке
105 107 /// </summary>
106 108 /// <param name="error">Исключение возникшее при выполнении операции</param>
107 109 /// <exception cref="InvalidOperationException">Данное обещание уже выполнено</exception>
108 110 public void Reject(Exception error) {
109 111 lock (this) {
110 if (m_state == State.Cancelled)
112 if (m_state == PromiseState.Cancelled)
111 113 return;
112 if (m_state != State.Unresolved)
114 if (m_state != PromiseState.Unresolved)
113 115 throw new InvalidOperationException("The promise is already resolved");
114 116 m_error = error;
115 m_state = State.Resolving;
117 m_state = PromiseState.Rejected;
116 118 }
117 119
118 120 ResultHandlerInfo handler;
119 121 while (FetchNextHandler(out handler))
120 122 InvokeHandler(handler);
121 123 }
122 124
123 125 /// <summary>
124 126 /// Отменяет операцию, если это возможно.
125 127 /// </summary>
126 128 /// <returns><c>true</c> Операция была отменена, обработчики не будут вызваны.<c>false</c> отмена не возможна, поскольку обещание уже выполнено и обработчики отработали.</returns>
127 129 public bool Cancel() {
128 130 lock (this) {
129 if (m_state == State.Unresolved && m_cancellable) {
130 m_state = State.Cancelled;
131 if (m_state == PromiseState.Unresolved && m_cancellable)
132 {
133 m_state = PromiseState.Cancelled;
131 134 EventHandler temp = Cancelled;
132 135
133 136 if (temp != null)
134 137 temp(this, new EventArgs());
135 138
136 139 return true;
137 140 } else
138 141 return false;
139 142 }
140 143 }
141 144
142 145 /// <summary>
143 146 /// Добавляет обработчики событий выполнения обещания.
144 147 /// </summary>
145 148 /// <param name="success">Обработчик успешного выполнения обещания.
146 149 /// Данному обработчику будет передан результат выполнения операции.</param>
147 150 /// <param name="error">Обработчик ошибки. Данный обработчик получит
148 151 /// исключение возникшее при выполнении операции.</param>
149 152 /// <returns>Само обещание</returns>
150 153 public Promise<T> Then(ResultHandler<T> success, ErrorHandler error) {
151 154 if (success == null && error == null)
152 155 return this;
153 156
154 157 var medium = new Promise<T>();
155 158
156 159 var handlerInfo = new ResultHandlerInfo();
157 160
158 161 if (success != null)
159 162 handlerInfo.resultHandler = x => {
160 163 try {
161 164 success(x);
162 165 medium.Resolve(x);
163 166 } catch (Exception e) {
164 167 medium.Reject(e);
165 168 }
166 169 };
167 170 else
168 171 handlerInfo.resultHandler = x => medium.Resolve(x);
169 172
170 173 if (error != null)
171 174 handlerInfo.errorHandler = x => {
172 175 try {
173 176 error(x);
174 177 } catch { }
175 178 medium.Reject(x);
176 179 };
177 180 else
178 181 handlerInfo.errorHandler = x => medium.Reject(x);
179 182
180 183 AddHandler(handlerInfo);
181 184
182 185 return medium;
183 186 }
184 187
185 188 public Promise<T> Then(ResultHandler<T> success) {
186 189 return Then(success, null);
187 190 }
188 191
189 192 public Promise<T> Error(ErrorHandler error) {
190 193 return Then(null, error);
191 194 }
192 195
193 196 public Promise<T> Anyway(Action handler) {
194 197 if (handler == null)
195 198 return this;
196 199
197 200 var medium = new Promise<T>();
198 201
199 202 AddHandler(new ResultHandlerInfo {
200 203 resultHandler = x => {
201 204 try {
202 205 handler();
203 206 medium.Resolve(x);
204 207 } catch (Exception e) {
205 208 medium.Reject(e);
206 209 }
207 210 },
208 211 errorHandler = x => {
209 212 try {
210 213 handler();
211 214 } catch { }
212 215 medium.Reject(x);
213 216 }
214 217 });
215 218
216 219 return medium;
217 220 }
218 221
219 222 /// <summary>
220 223 /// Позволяет преобразовать результат выполения операции к новому типу.
221 224 /// </summary>
222 225 /// <typeparam name="TNew">Новый тип результата.</typeparam>
223 226 /// <param name="mapper">Преобразование результата к новому типу.</param>
224 227 /// <param name="error">Обработчик ошибки. Данный обработчик получит
225 228 /// исключение возникшее при выполнении операции.</param>
226 229 /// <returns>Новое обещание, которое будет выполнено при выполнении исходного обещания.</returns>
227 230 public Promise<TNew> Map<TNew>(ResultMapper<T, TNew> mapper, ErrorHandler error) {
228 231 if (mapper == null)
229 232 throw new ArgumentNullException("mapper");
230 233
231 234 // создаем прицепленное обещание
232 235 Promise<TNew> chained = new Promise<TNew>();
233 236
234 237 AddHandler(new ResultHandlerInfo() {
235 238 resultHandler = delegate(T result) {
236 239 try {
237 240 // если преобразование выдаст исключение, то сработает reject сцепленного deferred
238 241 chained.Resolve(mapper(result));
239 242 } catch (Exception e) {
240 243 chained.Reject(e);
241 244 }
242 245 },
243 246 errorHandler = delegate(Exception e) {
244 247 if (error != null)
245 248 error(e);
246 249 // в случае ошибки нужно передать исключение дальше по цепочке
247 250 chained.Reject(e);
248 251 }
249 252 });
250 253
251 254 return chained;
252 255 }
253 256
254 257 public Promise<TNew> Map<TNew>(ResultMapper<T, TNew> mapper) {
255 258 return Map(mapper, null);
256 259 }
257 260
258 261 /// <summary>
259 262 /// Сцепляет несколько аснхронных операций. Указанная асинхронная операция будет вызвана после
260 263 /// выполнения текущей, а результат текущей операции может быть использован для инициализации
261 264 /// новой операции.
262 265 /// </summary>
263 266 /// <typeparam name="TNew">Тип результата указанной асинхронной операции.</typeparam>
264 267 /// <param name="chained">Асинхронная операция, которая должна будет начаться после выполнения текущей.</param>
265 268 /// <param name="error">Обработчик ошибки. Данный обработчик получит
266 269 /// исключение возникшее при выполнении текуещй операции.</param>
267 270 /// <returns>Новое обещание, которое будет выполнено по окончанию указанной аснхронной операции.</returns>
268 271 public Promise<TNew> Chain<TNew>(ChainedOperation<T, TNew> chained, ErrorHandler error) {
269 272
270 273 // проблема в том, что на момент связывания еще не начата асинхронная операция, поэтому нужно
271 274 // создать посредника, к которому будут подвызяваться следующие обработчики.
272 275 // когда будет выполнена реальная асинхронная операция, она обратиться к посреднику, чтобы
273 276 // передать через него результаты работы.
274 277 Promise<TNew> medium = new Promise<TNew>();
275 278
276 279 AddHandler(new ResultHandlerInfo() {
277 280 resultHandler = delegate(T result) {
278 281 try {
279 282 chained(result).Then(
280 283 x => medium.Resolve(x),
281 284 e => medium.Reject(e)
282 285 );
283 286 } catch (Exception e) {
284 287 // если сцепленное действие выдало исключение вместо обещания, то передаем ошибку по цепочке
285 288 medium.Reject(e);
286 289 }
287 290 },
288 291 errorHandler = delegate(Exception e) {
289 292 if (error != null)
290 293 error(e);
291 294 // в случае ошибки нужно передать исключение дальше по цепочке
292 295 medium.Reject(e);
293 296 }
294 297 });
295 298
296 299 return medium;
297 300 }
298 301
299 302 public Promise<TNew> Chain<TNew>(ChainedOperation<T, TNew> chained) {
300 303 return Chain(chained, null);
301 304 }
302 305
303 306 /// <summary>
304 307 /// Дожидается отложенного обещания и в случае успеха, возвращает
305 308 /// его, результат, в противном случае бросает исключение.
306 309 /// </summary>
307 310 /// <remarks>
308 311 /// <para>
309 312 /// Если ожидание обещания было прервано по таймауту, это не значит,
310 313 /// что обещание было отменено или что-то в этом роде, это только
311 314 /// означает, что мы его не дождались, однако все зарегистрированные
312 315 /// обработчики, как были так остались и они будут вызваны, когда
313 316 /// обещание будет выполнено.
314 317 /// </para>
315 318 /// <para>
316 319 /// Такое поведение вполне оправдано поскольку таймаут может истечь
317 320 /// в тот момент, когда началась обработка цепочки обработчиков, и
318 321 /// к тому же текущее обещание может стоять в цепочке обещаний и его
319 322 /// отклонение может привести к непрогнозируемому результату.
320 323 /// </para>
321 324 /// </remarks>
322 325 /// <param name="timeout">Время ожидания</param>
323 326 /// <returns>Результат выполнения обещания</returns>
324 327 public T Join(int timeout) {
325 328 ManualResetEvent evt = new ManualResetEvent(false);
326 329 Anyway(() => evt.Set());
327 330
328 331 if (!evt.WaitOne(timeout, true))
329 332 throw new TimeoutException();
330 333
331 334 if (m_error != null)
332 335 throw new TargetInvocationException(m_error);
333 336 else
334 337 return m_result;
335 338 }
336 339
337 340 public T Join() {
338 341 return Join(Timeout.Infinite);
339 342 }
340 343
341 344 /// <summary>
342 345 /// Данный метод последовательно извлекает обработчики обещания и когда
343 346 /// их больше не осталось - ставит состояние "разрешено".
344 347 /// </summary>
345 348 /// <param name="handler">Информация об обработчике</param>
346 349 /// <returns>Признак того, что еще остались обработчики в очереди</returns>
347 350 bool FetchNextHandler(out ResultHandlerInfo handler) {
348 351 handler = default(ResultHandlerInfo);
349 352
350 353 lock (this) {
351 Debug.Assert(m_state == State.Resolving);
354 Debug.Assert(m_state != PromiseState.Unresolved);
352 355
353 356 if (m_handlersChain.Count > 0) {
354 357 handler = m_handlersChain.First.Value;
355 358 m_handlersChain.RemoveFirst();
356 359 return true;
357 360 } else {
358 m_state = State.Resolved;
359 361 return false;
360 362 }
361 363 }
362 364 }
363 365
364 366 void AddHandler(ResultHandlerInfo handler) {
365 367 bool invokeRequired = false;
366 368
367 369 lock (this) {
368 if (m_state != State.Resolved)
370 if (m_state == PromiseState.Unresolved)
369 371 m_handlersChain.AddLast(handler);
370 372 else
371 373 invokeRequired = true;
372 374 }
373 375
374 376 // обработчики не должны блокировать сам объект
375 377 if (invokeRequired)
376 378 InvokeHandler(handler);
377 379 }
378 380
379 381 void InvokeHandler(ResultHandlerInfo handler) {
380 382 if (m_error == null) {
381 383 try {
382 384 if (handler.resultHandler != null)
383 385 handler.resultHandler(m_result);
384 386 } catch { }
385 387 }
386 388
387 389 if (m_error != null) {
388 390 try {
389 391 if (handler.errorHandler != null)
390 392 handler.errorHandler(m_error);
391 393 } catch { }
392 394 }
393 395 }
394 396
395 397
396 398 }
397 399 }
General Comments 0
You need to be logged in to leave comments. Login now