You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

138 line
5.8KB

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Threading;
  5. using System.Threading.Tasks;
  6. namespace TelpoPush.Common
  7. {
  8. /// <summary>
  9. /// Provides a task scheduler that ensures a maximum concurrency level while
  10. /// running on top of the ThreadPool.
  11. /// </summary>
  12. public class LimitedConcurrencyLevelTaskScheduler : TaskScheduler
  13. {
  14. /// <summary>Whether the current thread is processing work items.</summary>
  15. [ThreadStatic]
  16. private static bool _currentThreadIsProcessingItems;
  17. /// <summary>The list of tasks to be executed.</summary>
  18. private readonly LinkedList<Task> _tasks = new LinkedList<Task>(); // protected by lock(_tasks)
  19. /// <summary>The maximum concurrency level allowed by this scheduler.</summary>
  20. private readonly int _maxDegreeOfParallelism;
  21. /// <summary>Whether the scheduler is currently processing work items.</summary>
  22. private int _delegatesQueuedOrRunning = 0; // protected by lock(_tasks)
  23. /// <summary>
  24. /// Initializes an instance of the LimitedConcurrencyLevelTaskScheduler class with the
  25. /// specified degree of parallelism.
  26. /// </summary>
  27. /// <param name="maxDegreeOfParallelism">The maximum degree of parallelism provided by this scheduler.</param>
  28. public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism)
  29. {
  30. if (maxDegreeOfParallelism < 1) throw new ArgumentOutOfRangeException("maxDegreeOfParallelism");
  31. _maxDegreeOfParallelism = maxDegreeOfParallelism;
  32. }
  33. /// <summary>Queues a task to the scheduler.</summary>
  34. /// <param name="task">The task to be queued.</param>
  35. protected sealed override void QueueTask(Task task)
  36. {
  37. // Add the task to the list of tasks to be processed. If there aren't enough
  38. // delegates currently queued or running to process tasks, schedule another.
  39. lock (_tasks)
  40. {
  41. _tasks.AddLast(task);
  42. if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism)
  43. {
  44. ++_delegatesQueuedOrRunning;
  45. NotifyThreadPoolOfPendingWork();
  46. }
  47. }
  48. }
  49. /// <summary>
  50. /// Informs the ThreadPool that there's work to be executed for this scheduler.
  51. /// </summary>
  52. private void NotifyThreadPoolOfPendingWork()
  53. {
  54. ThreadPool.UnsafeQueueUserWorkItem(_ =>
  55. {
  56. // Note that the current thread is now processing work items.
  57. // This is necessary to enable inlining of tasks into this thread.
  58. _currentThreadIsProcessingItems = true;
  59. try
  60. {
  61. // Process all available items in the queue.
  62. while (true)
  63. {
  64. Task item;
  65. lock (_tasks)
  66. {
  67. // When there are no more items to be processed,
  68. // note that we're done processing, and get out.
  69. if (_tasks.Count == 0)
  70. {
  71. --_delegatesQueuedOrRunning;
  72. break;
  73. }
  74. // Get the next item from the queue
  75. item = _tasks.First.Value;
  76. _tasks.RemoveFirst();
  77. }
  78. // Execute the task we pulled out of the queue
  79. base.TryExecuteTask(item);
  80. }
  81. }
  82. // We're done processing items on the current thread
  83. finally { _currentThreadIsProcessingItems = false; }
  84. }, null);
  85. }
  86. /// <summary>Attempts to execute the specified task on the current thread.</summary>
  87. /// <param name="task">The task to be executed.</param>
  88. /// <param name="taskWasPreviouslyQueued"></param>
  89. /// <returns>Whether the task could be executed on the current thread.</returns>
  90. protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
  91. {
  92. // If this thread isn't already processing a task, we don't support inlining
  93. if (!_currentThreadIsProcessingItems) return false;
  94. // If the task was previously queued, remove it from the queue
  95. if (taskWasPreviouslyQueued) TryDequeue(task);
  96. // Try to run the task.
  97. return base.TryExecuteTask(task);
  98. }
  99. /// <summary>Attempts to remove a previously scheduled task from the scheduler.</summary>
  100. /// <param name="task">The task to be removed.</param>
  101. /// <returns>Whether the task could be found and removed.</returns>
  102. protected sealed override bool TryDequeue(Task task)
  103. {
  104. lock (_tasks) return _tasks.Remove(task);
  105. }
  106. /// <summary>Gets the maximum concurrency level supported by this scheduler.</summary>
  107. public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } }
  108. /// <summary>Gets an enumerable of the tasks currently scheduled on this scheduler.</summary>
  109. /// <returns>An enumerable of the tasks currently scheduled.</returns>
  110. protected sealed override IEnumerable<Task> GetScheduledTasks()
  111. {
  112. bool lockTaken = false;
  113. try
  114. {
  115. Monitor.TryEnter(_tasks, ref lockTaken);
  116. if (lockTaken) return _tasks.ToArray();
  117. else throw new NotSupportedException();
  118. }
  119. finally
  120. {
  121. if (lockTaken) Monitor.Exit(_tasks);
  122. }
  123. }
  124. }
  125. }