AsyncManager.cs 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. #region --------------- Copyright Dresser Wayne Pignone -------------
  2. /*
  3. * $Log: /Wrk/WayneLibraries/Wrk/AsyncManager/AsyncManager.cs $
  4. *
  5. * 12 08-03-19 12:09 Mattias.larsson
  6. * Added GetOperationsByData() and Clear().
  7. *
  8. * 11 08-01-31 12:12 Mattias.larsson
  9. *
  10. * 10 07-05-28 9:40 roger.månsson
  11. * Be more tolerant to null as user token.
  12. *
  13. * 9 07-04-16 12:37 roger.månsson
  14. * Added Data, as an application-defined extra value that can be stored
  15. * with the async operation.
  16. *
  17. * 8 07-03-22 9:53 Mattias.larsson
  18. *
  19. * 7 07-03-13 12:53 roger.månsson
  20. * Fixed the TryGetOperation return value when getting an operation but
  21. * with wrong type.
  22. *
  23. * 6 07-02-15 17:48 roger.månsson
  24. * FxCop updates
  25. *
  26. * 5 07-01-12 16:36 roger.månsson
  27. * Only log when actually cleaning operations from the internal list
  28. */
  29. #endregion
  30. using System;
  31. using System.Collections.Generic;
  32. using System.Linq;
  33. using System.Text;
  34. using Wayne.Lib.Log;
  35. namespace Wayne.Lib.AsyncManager
  36. {
  37. /// <summary>
  38. /// Async manager is used to handle asynchronous call tracking. It saves data about the
  39. /// asynchronous call so it can be signaled later on. This implementation is based on a operation id type that
  40. /// is defined by the type parameter TOperationId. For example if we are communicating with some equipment and
  41. /// there is a sequence number in the communication, this can be the operation id in the async manager. On the
  42. /// </summary>
  43. public class AsyncManager<TOperationId> : IIdentifiableEntity, IDisposable
  44. {
  45. #region Fields
  46. private int id;
  47. private IIdentifiableEntity parentEntity;
  48. private readonly IServiceLocator serviceLocator;
  49. private List<AsyncOperation<TOperationId>> outstandingOperationList = new List<AsyncOperation<TOperationId>>();
  50. private object outstandingOperationListLock = new object();
  51. private ITimer cleaningTimer;
  52. private TimeSpan cleanOutstandingOperationsOlderThan;
  53. private readonly Func<TOperationId> nextOperationId = null;
  54. #endregion
  55. #region Construction
  56. /// <summary>
  57. /// Initializes a new instance of the AsyncManager.
  58. /// Uses a default timeout of 1 hour.
  59. /// </summary>
  60. /// <param name="id">The Id.</param>
  61. /// <param name="parentEntity">The parent entity.</param>
  62. protected AsyncManager(int id, IIdentifiableEntity parentEntity)
  63. : this(id, parentEntity, ServiceContainerFactory.Create())
  64. {
  65. }
  66. /// <summary>
  67. /// Initializes a new instance of the AsyncManager.
  68. /// Uses a default timeout of 1 hour.
  69. /// </summary>
  70. /// <param name="id">The Id.</param>
  71. /// <param name="parentEntity">The parent entity.</param>
  72. /// <param name="serviceLocator"></param>
  73. protected AsyncManager(int id, IIdentifiableEntity parentEntity, IServiceLocator serviceLocator)
  74. : this(id, parentEntity, serviceLocator, TimeSpan.FromHours(1))
  75. {
  76. }
  77. /// <summary>
  78. /// Initializes a new instance of the AsyncManager with a specific cleanOutstandingOperations max age.
  79. /// The timer is triggered after a calculated period between 30 seconds and 10 minutes,
  80. /// if the TimeSpan is not null.
  81. /// </summary>
  82. /// <param name="id">The Id.</param>
  83. /// <param name="parentEntity">The parent entity.</param>
  84. /// <param name="serviceLocator"></param>
  85. /// <param name="cleanOutstandingOperationsOlderThan">Sets the maximum age an operation can achieve. Minimum age is 1 minute.
  86. /// If null no timer is created.</param>
  87. protected AsyncManager(int id, IIdentifiableEntity parentEntity, IServiceLocator serviceLocator, TimeSpan? cleanOutstandingOperationsOlderThan)
  88. {
  89. this.id = id;
  90. this.parentEntity = parentEntity;
  91. this.serviceLocator = serviceLocator;
  92. ITimerFactory timerFactory = serviceLocator.GetServiceOrDefault<ITimerFactory>(() => new WayneTimerFactory());
  93. if (cleanOutstandingOperationsOlderThan.HasValue)
  94. {
  95. this.cleanOutstandingOperationsOlderThan = cleanOutstandingOperationsOlderThan.Value;
  96. TimeSpan timerDurationAndPeriod =
  97. CalculatecleaningTimerTriggerLengthFromcleanOutstandingOperationsOlderThan();
  98. cleaningTimer = timerFactory.Create(IdentifiableEntity.NoId, this, "CleaningTimer");
  99. cleaningTimer.OnTimeout += CleanOutstandingOperations;
  100. cleaningTimer.Change(timerDurationAndPeriod, timerDurationAndPeriod);
  101. }
  102. }
  103. /// <summary>
  104. /// Initializes a new instance of the AsyncManager with a specific cleanOutstandingOperations max age.
  105. /// The timer is triggered after a calculated period between 30 seconds and 10 minutes,
  106. /// if the TimeSpan is not null.
  107. /// </summary>
  108. /// <param name="id">The Id.</param>
  109. /// <param name="parentEntity">The parent entity.</param>
  110. /// <param name="serviceLocator"></param>
  111. /// <param name="cleanOutstandingOperationsOlderThan">Sets the maximum age an operation can achieve. Minimum age is 1 minute.
  112. /// If null no timer is created.</param>
  113. /// <param name="nextOperationId"></param>
  114. public AsyncManager(int id, IIdentifiableEntity parentEntity, IServiceLocator serviceLocator, TimeSpan? cleanOutstandingOperationsOlderThan, Func<TOperationId> nextOperationId)
  115. {
  116. this.id = id;
  117. this.parentEntity = parentEntity;
  118. this.serviceLocator = serviceLocator;
  119. this.nextOperationId = nextOperationId;
  120. ITimerFactory timerFactory = serviceLocator.GetServiceOrDefault<ITimerFactory>(() => new WayneTimerFactory());
  121. if (cleanOutstandingOperationsOlderThan.HasValue)
  122. {
  123. this.cleanOutstandingOperationsOlderThan = cleanOutstandingOperationsOlderThan.Value;
  124. TimeSpan timerDurationAndPeriod =
  125. CalculatecleaningTimerTriggerLengthFromcleanOutstandingOperationsOlderThan();
  126. cleaningTimer = timerFactory.Create(IdentifiableEntity.NoId, this, "CleaningTimer");
  127. cleaningTimer.OnTimeout += CleanOutstandingOperations;
  128. cleaningTimer.Change(timerDurationAndPeriod, timerDurationAndPeriod);
  129. }
  130. }
  131. /// <summary>
  132. /// Finalizer
  133. /// </summary>
  134. ~AsyncManager()
  135. {
  136. Dispose(false);
  137. }
  138. #endregion
  139. #region Protected virtual methods
  140. /// <summary>
  141. /// Method that should be overridden by descendant classes in order to create
  142. /// a new operation id to be used by the next operation.
  143. /// </summary>
  144. /// <returns></returns>
  145. protected virtual TOperationId CreateNextOperationId()
  146. {
  147. return nextOperationId();
  148. }
  149. #endregion
  150. #region Public Methods
  151. /// <summary>
  152. /// Registers an operation with the owner, result delegate and a user token, so it can be found later on. It creates an operation id that can be read from the AsyncOperation that
  153. /// is returned from the function.
  154. /// </summary>
  155. /// <typeparam name="TResultEventArgs">Type of the EventArgs to the result delegate.</typeparam>
  156. /// <param name="owner">The object that is going to be set as sender in the result delegate invocation.</param>
  157. /// <param name="resultDelegate">Delegate to be</param>
  158. /// <param name="userToken">User token that is returned in the calback invokation.</param>
  159. public AsyncOperation<TOperationId, TResultEventArgs> RegisterOperation<TResultEventArgs>(object owner, EventHandler<TResultEventArgs> resultDelegate, object userToken) where TResultEventArgs : EventArgs
  160. {
  161. return RegisterOperation(owner, resultDelegate, userToken, null);
  162. }
  163. /// <summary>
  164. /// Registers an operation with the owner, result delegate and a user token, so it can be found later on. It creates an operation id that can be read from the AsyncOperation that
  165. /// is returned from the function. The application can an application-defined object that is to be stored with the operation.
  166. /// </summary>
  167. /// <typeparam name="TResultEventArgs">Type of the EventArgs to the result delegate.</typeparam>
  168. /// <param name="owner">The object that is going to be set as sender in the result delegate invocation.</param>
  169. /// <param name="resultDelegate">Delegate to be</param>
  170. /// <param name="userToken">User token that is returned in the calback invokation.</param>
  171. /// <param name="data">Application defined data.</param>
  172. public AsyncOperation<TOperationId, TResultEventArgs> RegisterOperation<TResultEventArgs>(object owner, EventHandler<TResultEventArgs> resultDelegate, object userToken, object data) where TResultEventArgs : EventArgs
  173. {
  174. lock (outstandingOperationListLock)
  175. {
  176. TOperationId newOperationId = CreateNextOperationId();
  177. AsyncOperation<TOperationId, TResultEventArgs> asyncOperation = new AsyncOperation<TOperationId, TResultEventArgs>(owner, newOperationId, userToken, data, resultDelegate, cleanOutstandingOperationsOlderThan);
  178. outstandingOperationList.Add(asyncOperation);
  179. asyncOperation.OnOperationCompleted += asyncOperation_OnOperationCompleted;
  180. return asyncOperation;
  181. }
  182. }
  183. /// <summary>
  184. /// Retrieves an outstanding operation by the operation id
  185. /// </summary>
  186. /// <typeparam name="TResultEventArgs">The type that the operation is expected to be containing, so we know what we expect to get as a result.</typeparam>
  187. /// <param name="operationId">The operation Id to search for.</param>
  188. /// <param name="asyncOperation">Out parameter that returns the matching operation. Null if it is not found.</param>
  189. /// <returns>True if a matching operation is found, otherwise false.</returns>
  190. public bool TryGetOperation<TResultEventArgs>(TOperationId operationId, out AsyncOperation<TOperationId, TResultEventArgs> asyncOperation) where TResultEventArgs : EventArgs
  191. {
  192. asyncOperation = null;
  193. lock (outstandingOperationListLock)
  194. {
  195. foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
  196. {
  197. if ((op.Id.Equals(operationId)))
  198. {
  199. asyncOperation = op as AsyncOperation<TOperationId, TResultEventArgs>;
  200. if (asyncOperation != null)
  201. return true;
  202. else
  203. return false;
  204. }
  205. }
  206. }
  207. return false;
  208. }
  209. /// <summary>
  210. /// Checks whether the requested Operation exists in the list.
  211. /// </summary>
  212. /// <param name="operationId">The operation Id to search for.</param>
  213. /// <returns>True if a matching operation is found, otherwise false.</returns>
  214. public bool OperationExists(TOperationId operationId)
  215. {
  216. lock (outstandingOperationListLock)
  217. {
  218. foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
  219. if ((op.Id.Equals(operationId)))
  220. return true;
  221. }
  222. return false;
  223. }
  224. /// <summary>
  225. /// Returns the EventArgs-type if a matching operation is found, otherwise null.
  226. /// </summary>
  227. /// <param name="operationId">The operation Id to search for.</param>
  228. /// <returns>The EventArgs-type if a matching operation is found, otherwise null.</returns>
  229. public Type GetOperationType(TOperationId operationId)
  230. {
  231. lock (outstandingOperationListLock)
  232. {
  233. foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
  234. if ((op.Id.Equals(operationId)))
  235. return op.ResultEventArgsType;
  236. }
  237. return null;
  238. }
  239. /// <summary>
  240. /// Gets a list of outstanding operations that matches the the specified userToken. Checks and converts the operation to the specified type.
  241. /// </summary>
  242. /// <typeparam name="TResultEventArgs">Type of the result event args.</typeparam>
  243. /// <param name="userToken">User token to match the operation against.</param>
  244. /// <param name="asyncOperationList">List of matching operations.</param>
  245. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
  246. public void GetOperationsByUserToken<TResultEventArgs>(object userToken, out AsyncOperation<TOperationId, TResultEventArgs>[] asyncOperationList) where TResultEventArgs : EventArgs
  247. {
  248. List<AsyncOperation<TOperationId, TResultEventArgs>> list = new List<AsyncOperation<TOperationId, TResultEventArgs>>();
  249. lock (outstandingOperationListLock)
  250. {
  251. foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
  252. {
  253. if (op.UserToken != null)
  254. {
  255. if ((op.UserToken.Equals(userToken)))
  256. {
  257. AsyncOperation<TOperationId, TResultEventArgs> asyncOperation = op as AsyncOperation<TOperationId, TResultEventArgs>;
  258. if (asyncOperation != null)
  259. list.Add(asyncOperation);
  260. }
  261. }
  262. else
  263. {
  264. if (userToken == null)
  265. {
  266. AsyncOperation<TOperationId, TResultEventArgs> asyncOperation = op as AsyncOperation<TOperationId, TResultEventArgs>;
  267. if (asyncOperation != null)
  268. list.Add(asyncOperation);
  269. }
  270. }
  271. }
  272. }
  273. asyncOperationList = list.ToArray();
  274. }
  275. /// <summary>
  276. /// Gets an outstanding operation list using the userToken as argument. Does not check the type of the operation.
  277. /// </summary>
  278. /// <param name="userToken">User token that is specified for the outstanding operation.</param>
  279. /// <returns>A list of Async operations.</returns>
  280. public AsyncOperation<TOperationId>[] GetOperationsByUserToken(object userToken)
  281. {
  282. List<AsyncOperation<TOperationId>> list = new List<AsyncOperation<TOperationId>>();
  283. lock (outstandingOperationListLock)
  284. {
  285. foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
  286. {
  287. if (op.UserToken != null)
  288. {
  289. if ((op.UserToken.Equals(userToken)))
  290. {
  291. list.Add(op);
  292. }
  293. }
  294. else
  295. {
  296. if (userToken == null) //If the requesting user token also is null, it is a valid match!
  297. list.Add(op);
  298. }
  299. }
  300. }
  301. return list.ToArray();
  302. }
  303. /// <summary>
  304. /// Gets a list of outstanding operations that matches the the specified data object. Checks and converts the operation to the specified type.
  305. /// </summary>
  306. /// <typeparam name="TResultEventArgs">Type of the result event args.</typeparam>
  307. /// <param name="data">Data object to match the operation against.</param>
  308. /// <param name="asyncOperationList">List of matching operations.</param>
  309. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters", MessageId = "1#")]
  310. public void GetOperationsByData<TResultEventArgs>(object data, out AsyncOperation<TOperationId, TResultEventArgs>[] asyncOperationList) where TResultEventArgs : EventArgs
  311. {
  312. List<AsyncOperation<TOperationId, TResultEventArgs>> list = new List<AsyncOperation<TOperationId, TResultEventArgs>>();
  313. lock (outstandingOperationListLock)
  314. {
  315. foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
  316. {
  317. if (op.Data != null)
  318. {
  319. if ((op.Data.Equals(data)))
  320. {
  321. AsyncOperation<TOperationId, TResultEventArgs> asyncOperation = op as AsyncOperation<TOperationId, TResultEventArgs>;
  322. if (asyncOperation != null)
  323. list.Add(asyncOperation);
  324. }
  325. }
  326. else
  327. {
  328. if (data == null)
  329. {
  330. AsyncOperation<TOperationId, TResultEventArgs> asyncOperation = op as AsyncOperation<TOperationId, TResultEventArgs>;
  331. if (asyncOperation != null)
  332. list.Add(asyncOperation);
  333. }
  334. }
  335. }
  336. }
  337. asyncOperationList = list.ToArray();
  338. }
  339. /// <summary>
  340. /// Gets an outstanding operation list using the data object as argument. Does not check the type of the operation.
  341. /// </summary>
  342. /// <param name="data">User token that is specified for the outstanding operation.</param>
  343. /// <returns>A list of Async operations.</returns>
  344. public AsyncOperation<TOperationId>[] GetOperationsByData(object data)
  345. {
  346. List<AsyncOperation<TOperationId>> list = new List<AsyncOperation<TOperationId>>();
  347. lock (outstandingOperationListLock)
  348. {
  349. foreach (AsyncOperation<TOperationId> op in outstandingOperationList)
  350. {
  351. if (op.Data != null)
  352. {
  353. if ((op.Data.Equals(data)))
  354. {
  355. list.Add(op);
  356. }
  357. }
  358. else
  359. {
  360. if (data == null) //If the requesting user token also is null, it is a valid match!
  361. list.Add(op);
  362. }
  363. }
  364. }
  365. return list.ToArray();
  366. }
  367. /// <summary>
  368. /// Clear all outstanding operations.
  369. /// </summary>
  370. public void Clear()
  371. {
  372. lock (outstandingOperationListLock)
  373. {
  374. outstandingOperationList.Clear();
  375. }
  376. }
  377. #endregion
  378. #region Private Methods
  379. /// <summary>
  380. /// Calculates the timespan based on simple rules.
  381. /// </summary>
  382. /// <returns>The timespan object to be used for the cleaningTimers period and duration</returns>
  383. private TimeSpan CalculatecleaningTimerTriggerLengthFromcleanOutstandingOperationsOlderThan()
  384. {
  385. TimeSpan result;
  386. if (cleanOutstandingOperationsOlderThan >= TimeSpan.FromHours(1))
  387. {
  388. result = TimeSpan.FromMinutes(10);
  389. }
  390. else if (cleanOutstandingOperationsOlderThan <= TimeSpan.FromMinutes(1))
  391. {
  392. result = TimeSpan.FromSeconds(30);
  393. //Will never accept less values than 1 minute as oldest time an operation might be.
  394. cleanOutstandingOperationsOlderThan = TimeSpan.FromMinutes(1);
  395. }
  396. else
  397. {
  398. //The formula is:
  399. // x = y - b / m
  400. // m = (y2 - y1) / (x2 - x1) => (3600 * 10^3 - 60 * 10^3) / (600 * 10 ^3 - 30 * 10^3) => (354/57) ~= 6,2105...
  401. // b = y1 - m * x1 => 60 * 10^3 - (354/57) * 30 * 10^3 ~= -126315,789476...
  402. result = TimeSpan.FromMilliseconds((cleanOutstandingOperationsOlderThan.TotalMilliseconds + 126315.78947368421052631578947368) / (354.0 / 57.0));
  403. }
  404. return result;
  405. }
  406. /// <summary>
  407. /// Event handler that is called when an asynchronous operation has completed. It will remove the
  408. /// operation from the outstanding operations list.
  409. /// </summary>
  410. /// <param name="sender"></param>
  411. /// <param name="e"></param>
  412. void asyncOperation_OnOperationCompleted(object sender, EventArgs e)
  413. {
  414. AsyncOperation<TOperationId> asyncOperation = sender as AsyncOperation<TOperationId>;
  415. if (asyncOperation != null)
  416. {
  417. lock (outstandingOperationListLock)
  418. {
  419. if (outstandingOperationList.Contains(asyncOperation))
  420. {
  421. outstandingOperationList.Remove(asyncOperation);
  422. }
  423. }
  424. }
  425. }
  426. /// <summary>
  427. /// Timer method that is called to clean the outstanding queue. For now, the requests must not stay
  428. /// more than 1h in the outstanding list.
  429. /// </summary>
  430. /// <param name="sender"></param>
  431. /// <param name="e"></param>
  432. void CleanOutstandingOperations(object sender, EventArgs e)
  433. {
  434. AsyncOperation<TOperationId>[] abandonedOperations;
  435. lock (outstandingOperationListLock)
  436. {
  437. abandonedOperations = outstandingOperationList.Where(x => x.Timeout.IsTimedOut).ToArray();
  438. }
  439. using (DebugLogger debugLogger = new DebugLogger(this))
  440. {
  441. if (abandonedOperations.Length > 0)
  442. {
  443. //Remove the items that should be removed.
  444. if (debugLogger.IsActive())
  445. {
  446. debugLogger.Add("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO");
  447. debugLogger.Add("Cleaning the outstanding operations.");
  448. debugLogger.Add("---------------------------------------------------");
  449. }
  450. foreach (AsyncOperation<TOperationId> abandonedOperation in abandonedOperations)
  451. {
  452. if (debugLogger.IsActive())
  453. {
  454. debugLogger.Add("Removing operation: ");
  455. debugLogger.Add(abandonedOperation.ToString());
  456. }
  457. try
  458. {
  459. abandonedOperation.Abandoned();
  460. }
  461. catch (Exception exception)
  462. {
  463. //Logger.AddExceptionLogEntry(new ExceptionLogEntry(this, ErrorLogSeverity.Recoverable, "Exception when handling abandoned async operation " + abandonedOperation, exception));
  464. }
  465. }
  466. if (debugLogger.IsActive())
  467. debugLogger.Add("OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO");
  468. }
  469. }
  470. //Remove the found operations, if not removed by abandoned- logic.
  471. lock (outstandingOperationListLock)
  472. {
  473. abandonedOperations.ForEach(x => outstandingOperationList.Remove(x));
  474. }
  475. }
  476. #endregion
  477. #region IIdentifiableEntity Members
  478. /// <summary>
  479. /// Async manager Id (for logging)
  480. /// </summary>
  481. public int Id
  482. {
  483. get { return id; }
  484. }
  485. /// <summary>
  486. /// Entity type (for logging)
  487. /// </summary>
  488. public string EntityType
  489. {
  490. get { return "AsyncManager"; }
  491. }
  492. /// <summary>
  493. /// This is used by the logger and should never be set by inheriting classes
  494. /// </summary>
  495. public string FullEntityName { get; set; }
  496. /// <summary>
  497. /// Entity sub type (for logging)
  498. /// </summary>
  499. public virtual string EntitySubType
  500. {
  501. get { return ""; }
  502. }
  503. /// <summary>
  504. /// Parent entity (for logging)
  505. /// </summary>
  506. public IIdentifiableEntity ParentEntity
  507. {
  508. get { return parentEntity; }
  509. }
  510. #endregion
  511. #region IDisposable Members
  512. /// <summary>
  513. /// Dispsose.
  514. /// </summary>
  515. /// <param name="disposing"></param>
  516. protected virtual void Dispose(bool disposing)
  517. {
  518. if (disposing)
  519. {
  520. if (cleaningTimer != null)
  521. cleaningTimer.Dispose();
  522. }
  523. }
  524. /// <summary>
  525. /// Dispsose.
  526. /// </summary>
  527. public void Dispose()
  528. {
  529. Dispose(true);
  530. GC.SuppressFinalize(this);
  531. }
  532. #endregion
  533. }
  534. }