PageRenderTime 50ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/Phone/Galador.Utils/Galador.Utils/PriorityThreadPool.cs

#
C# | 533 lines | 439 code | 57 blank | 37 comment | 50 complexity | e6e87c661438ca45452accee10f04865 MD5 | raw file
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Threading;
  6. using System.Diagnostics;
  7. namespace Galador.Utils
  8. {
  9. public class PriorityThreadPool : IDisposable
  10. {
  11. public PriorityThreadPool()
  12. {
  13. IsBackground = true;
  14. ThreadCount = Environment.ProcessorCount;
  15. }
  16. #region public static PriorityThreadPool Shared
  17. public static PriorityThreadPool Shared { get { return cShared; } }
  18. static PriorityThreadPool cShared = new PriorityThreadPool() { ThreadCount = Environment.ProcessorCount, IsBackground = true };
  19. #endregion
  20. #region ThreadName, IsBackground, ExceptionHandler, SyncLock
  21. /// <summary>
  22. /// A name that would be used for the pool's new thread's name.
  23. /// </summary>
  24. public string ThreadName { get; set; }
  25. /// <summary>
  26. /// The value use for the pool's new thread's IsBackground property.
  27. /// </summary>
  28. public bool IsBackground { get; set; }
  29. /// <summary>
  30. /// Handle the exception. Return true if it has been handled, or false to rethrow.
  31. /// </summary>
  32. public Predicate<Exception> ExceptionHandler { get; set; }
  33. /// <summary>
  34. /// The lock use internally for managing the pool's threads.
  35. /// </summary>
  36. public object SyncLock { get { return queueLock; } }
  37. #endregion
  38. #region QueueUserWorkItem
  39. public void QueueUserWorkItem(
  40. int priority,
  41. WaitCallback callBack)
  42. {
  43. QueueUserWorkItem(priority, callBack, null);
  44. }
  45. public void QueueUserWorkItem(
  46. int priority,
  47. WaitCallback callBack,
  48. object state)
  49. {
  50. if (callBack == null)
  51. return;
  52. var q = new QueueItem
  53. {
  54. Priority = priority,
  55. Callback = callBack,
  56. State = state
  57. };
  58. Enqueue(q);
  59. }
  60. #endregion
  61. #region ThreadCount, IdleThreadCount, BusyThreadCount, PendingItemCount
  62. int threadCount = 4;
  63. /// <summary>
  64. /// Maximum number of thread to create. 0 Will stop it. This is a control parameter.
  65. /// The pool can have less thread if it has been stressed yet or more thread if the value has just been changed.
  66. /// </summary>
  67. public int ThreadCount
  68. {
  69. get { return threadCount; }
  70. set
  71. {
  72. lock (queueLock)
  73. {
  74. if (IsDisposed)
  75. throw new ObjectDisposedException(GetType().FullName);
  76. if (value == threadCount)
  77. return;
  78. if (value < 0 || value > 50)
  79. throw new ArgumentOutOfRangeException("value");
  80. threadCount = value;
  81. wait.Set();
  82. }
  83. UpdateIdleInfo();
  84. }
  85. }
  86. /// <summary>
  87. /// The idle thread count is the number of thread which have nothing to do for the foreseeable future.
  88. /// It's not <c>ThreadCount - BusyThreadCount</c> as it include information about pending task too.
  89. /// </summary>
  90. public int IdleThreadCount
  91. {
  92. get
  93. {
  94. lock (queueLock)
  95. {
  96. if (IsDisposed)
  97. return 0;
  98. int F = ThreadCount - threads.Count;
  99. F += (from t in threads where !t.IsRunning select t).Count();
  100. F -= pending.Count;
  101. if (F < 0)
  102. F = 0;
  103. return F;
  104. }
  105. }
  106. }
  107. /// <summary>
  108. /// The number of thread currently working
  109. /// </summary>
  110. public int BusyThreadCount
  111. {
  112. get
  113. {
  114. lock (queueLock)
  115. {
  116. if (IsDisposed)
  117. return 0;
  118. return (from t in threads where t.IsRunning select t).Count();
  119. }
  120. }
  121. }
  122. public int PendingItemCount
  123. {
  124. get
  125. {
  126. lock (queueLock)
  127. {
  128. if (IsDisposed)
  129. return 0;
  130. return pending.Count;
  131. }
  132. }
  133. }
  134. #endregion
  135. #region HasIdleThread, HasIdleThreadChanged, Idle, IdleChanged
  136. object idleLock = new object();
  137. bool hasIdleThread = true;
  138. public bool HasIdleThread
  139. {
  140. get { return hasIdleThread; }
  141. private set
  142. {
  143. lock (idleLock)
  144. {
  145. if (value == hasIdleThread)
  146. return;
  147. hasIdleThread = value;
  148. }
  149. OnHasIdleThreadChanged();
  150. }
  151. }
  152. public event EventHandler HasIdleThreadChanged
  153. {
  154. add
  155. {
  156. lock (idleLock)
  157. hasIdleThreadEvent += value;
  158. }
  159. remove
  160. {
  161. lock (idleLock)
  162. hasIdleThreadEvent -= value;
  163. }
  164. }
  165. EventHandler hasIdleThreadEvent;
  166. void OnHasIdleThreadChanged()
  167. {
  168. EventHandler ev;
  169. lock (idleLock)
  170. ev = hasIdleThreadEvent;
  171. if (ev != null)
  172. try
  173. {
  174. ev(this, EventArgs.Empty);
  175. }
  176. catch (Exception ex)
  177. {
  178. var h = ExceptionHandler;
  179. if (h == null || !h(ex))
  180. {
  181. // remove the dead thread
  182. lock (queueLock)
  183. threads.RemoveAll(ti => ti.Thread == Thread.CurrentThread);
  184. throw;
  185. }
  186. }
  187. }
  188. /// <summary>
  189. /// Returns true if there are no pending and running action.
  190. /// </summary>
  191. public bool Idle
  192. {
  193. get { return isIdle; }
  194. private set
  195. {
  196. lock (idleLock)
  197. {
  198. if (value == isIdle)
  199. return;
  200. isIdle = value;
  201. }
  202. OnIdleChanged();
  203. }
  204. }
  205. bool isIdle;
  206. public event EventHandler IdleChanged
  207. {
  208. add
  209. {
  210. lock (idleLock)
  211. idleChanged += value;
  212. }
  213. remove
  214. {
  215. lock (idleLock)
  216. idleChanged -= value;
  217. }
  218. }
  219. EventHandler idleChanged;
  220. void OnIdleChanged()
  221. {
  222. EventHandler ev;
  223. lock (idleLock)
  224. ev = idleChanged;
  225. if (ev != null)
  226. try
  227. {
  228. ev(this, EventArgs.Empty);
  229. }
  230. catch (Exception ex)
  231. {
  232. var h = ExceptionHandler;
  233. if (h == null || !h(ex))
  234. {
  235. // remove the dead thread
  236. lock (queueLock)
  237. threads.RemoveAll(ti => ti.Thread == Thread.CurrentThread);
  238. throw;
  239. }
  240. }
  241. }
  242. void UpdateIdleInfo()
  243. {
  244. bool idleThread, idle = true;
  245. lock (queueLock)
  246. {
  247. if (pending.Count > 0)
  248. {
  249. idle = false;
  250. }
  251. else
  252. {
  253. foreach (var item in threads)
  254. {
  255. if (item.IsRunning)
  256. {
  257. idle = false;
  258. break;
  259. }
  260. }
  261. }
  262. idleThread = IdleThreadCount > 0;
  263. }
  264. HasIdleThread = idleThread;
  265. Idle = idle;
  266. }
  267. #endregion
  268. #region inner workings
  269. // struct are light, although, arguable, the WaitCallback is going to be heavy...
  270. struct QueueItem
  271. {
  272. public int Priority;
  273. public WaitCallback Callback;
  274. public object State;
  275. }
  276. // it is a class, so it is passed as reference, so it can be found in the list
  277. class ThreadItem
  278. {
  279. public Thread Thread;
  280. public QueueItem? Item;
  281. public bool IsRunning { get { return Item.HasValue; } }
  282. }
  283. object queueLock = new object();
  284. ManualResetEvent wait = new ManualResetEvent(true);
  285. List<ThreadItem> threads = new List<ThreadItem>();
  286. List<QueueItem> pending = new List<QueueItem>();
  287. void Enqueue(QueueItem qi)
  288. {
  289. lock (queueLock)
  290. {
  291. if (IsDisposed)
  292. throw new ObjectDisposedException(GetType().FullName);
  293. // test if a new thread needs to be created
  294. int nRunning = 1 + pending.Count + (from t in threads where t.IsRunning select t).Count();
  295. if (nRunning > threads.Count && threads.Count < ThreadCount)
  296. {
  297. var name = ThreadName;
  298. var item = new ThreadItem
  299. {
  300. Thread = new Thread(ThreadRun)
  301. {
  302. IsBackground = this.IsBackground,
  303. Name = !string.IsNullOrEmpty(name) ? (name + " - " + threads.Count) : null
  304. }
  305. };
  306. threads.Add(item);
  307. item.Thread.Start(item);
  308. }
  309. // do not reuse the thread, always queue the item
  310. pending.Add(qi);
  311. UpdateIdleInfo();
  312. // release threads
  313. wait.Set();
  314. }
  315. // give a chance to the other thread to catch up!
  316. Thread.Sleep(0);
  317. }
  318. void TryDequeue(ThreadItem target)
  319. {
  320. lock (queueLock)
  321. {
  322. if (pending.Count == 0)
  323. {
  324. wait.Reset();
  325. target.Item = null;
  326. return;
  327. }
  328. int imax = 0;
  329. int maxp = pending[0].Priority;
  330. for (int i = 1; i < pending.Count; i++)
  331. {
  332. if (pending[i].Priority > maxp)
  333. {
  334. imax = i;
  335. maxp = pending[i].Priority;
  336. }
  337. }
  338. target.Item = pending[imax];
  339. pending.RemoveAt(imax);
  340. UpdateIdleInfo();
  341. }
  342. }
  343. void ThreadRun(object state)
  344. {
  345. var self = (ThreadItem)state;
  346. try
  347. {
  348. while (true)
  349. {
  350. lock (queueLock)
  351. {
  352. if (threads.Count > threadCount)
  353. return;
  354. TryDequeue(self); // careful with Dequeue() it Reset() the wait handle
  355. }
  356. if (self.IsRunning)
  357. ClientRun(self);
  358. lock (queueLock)
  359. {
  360. if (IsDisposed)
  361. return;
  362. self.Item = null;
  363. UpdateIdleInfo();
  364. // ThreadCount has reduced... some thread should die...
  365. if (threads.Count > threadCount)
  366. return;
  367. if (pending.Count > 0)
  368. continue;
  369. }
  370. wait.WaitOne();
  371. }
  372. }
  373. catch (ObjectDisposedException) { return; }
  374. finally
  375. {
  376. lock (queueLock)
  377. threads.Remove(self);
  378. UpdateIdleInfo();
  379. }
  380. }
  381. void ClientRun(ThreadItem ti)
  382. {
  383. try
  384. {
  385. var qi = ti.Item.Value;
  386. qi.Callback(qi.State);
  387. }
  388. catch(Exception ex)
  389. {
  390. var h = ExceptionHandler;
  391. if (h == null || !h(ex))
  392. {
  393. // remove the dead thread
  394. lock (queueLock)
  395. threads.Remove(ti);
  396. throw;
  397. }
  398. }
  399. }
  400. #endregion
  401. #region IDisposable
  402. public bool IsDisposed { get; private set; }
  403. public void Dispose()
  404. {
  405. // gentle quit...
  406. lock (queueLock)
  407. {
  408. ThreadCount = 0;
  409. pending.Clear();
  410. IsDisposed = true;
  411. }
  412. wait.Set();
  413. wait.Close();
  414. }
  415. #endregion
  416. #region ToString() (print some information on the threads in the pool)
  417. public override string ToString()
  418. {
  419. StringBuilder sb = new StringBuilder();
  420. lock (queueLock)
  421. {
  422. sb.Append("PriorityThreadPool[thread (idle / total): ");
  423. sb.Append(IdleThreadCount);
  424. sb.Append("/");
  425. sb.Append(ThreadCount);
  426. sb.Append("]");
  427. sb.AppendLine();
  428. foreach (var item in threads)
  429. {
  430. if (!item.IsRunning)
  431. continue;
  432. sb.Append("\t");
  433. sb.Append(item.Thread.Name);
  434. sb.AppendLine();
  435. StackTrace stack;
  436. if (Thread.CurrentThread == item.Thread)
  437. {
  438. stack = new StackTrace(true);
  439. }
  440. else
  441. {
  442. #pragma warning disable 618
  443. item.Thread.Suspend();
  444. try
  445. {
  446. stack = new StackTrace(item.Thread, true);
  447. }
  448. finally
  449. {
  450. item.Thread.Resume();
  451. }
  452. #pragma warning restore 618
  453. }
  454. sb.Append(stack);
  455. }
  456. }
  457. return sb.ToString();
  458. }
  459. #endregion
  460. }
  461. #if SILVERLIGHT
  462. static class PoolEx
  463. {
  464. public static void RemoveAll<T>(this List<T> list, Predicate<T> toRemove)
  465. {
  466. for (int i = list.Count - 1; i >= 0; i--)
  467. if (toRemove(list[i]))
  468. list.RemoveAt(i);
  469. }
  470. [Conditional("USELESS")]
  471. public static void Suspend(this Thread t) { }
  472. [Conditional("USELESS")]
  473. public static void Resume(this Thread t) { }
  474. }
  475. #endif
  476. }