/// <summary>Adds a work-stealing queue to the set of queues.</summary> /// <param name="wsq">The queue to be added.</param> private void AddWsq(WorkStealingQueue <Task> wsq) { lock (m_wsQueues) { // Find the next open slot in the array. If we find one, // store the queue and we're done. int i; for (i = 0; i < m_wsQueues.Length; i++) { if (m_wsQueues[i] == null) { m_wsQueues[i] = wsq; return; } } // We couldn't find an open slot, so double the length // of the array by creating a new one, copying over, // and storing the new one. Here, i == m_wsQueues.Length. WorkStealingQueue <Task>[] queues = new WorkStealingQueue <Task> [i * 2]; Array.Copy(m_wsQueues, queues, i); queues[i] = wsq; m_wsQueues = queues; } }
/// <summary>Remove a work-stealing queue from the set of queues.</summary> /// <param name="wsq">The work-stealing queue to remove.</param> private void RemoveWsq(WorkStealingQueue <Task> wsq) { lock (m_wsQueues) { // Find the queue, and if/when we find it, null out its array slot for (int i = 0; i < m_wsQueues.Length; i++) { if (m_wsQueues[i] == wsq) { m_wsQueues[i] = null; } } } }
/// <summary>Queues a task to the scheduler.</summary> /// <param name="task">The task to be scheduled.</param> protected override void QueueTask(Task task) { // Make sure the pool is started, e.g. that all threads have been created. m_threads.Force(); // If the task is marked as long-running, give it its own dedicated thread // rather than queueing it. if ((task.CreationOptions & TaskCreationOptions.LongRunning) != 0) { new Thread(state => base.TryExecuteTask((Task)state)) { IsBackground = true }.Start(task); } else { // Otherwise, insert the work item into a queue, possibly waking a thread. // If there's a local queue and the task does not prefer to be in the global queue, // add it to the local queue. WorkStealingQueue <Task> wsq = m_wsq; if (wsq != null && ((task.CreationOptions & TaskCreationOptions.PreferFairness) == 0)) { // Add to the local queue and notify any waiting threads that work is available. // Races may occur which result in missed event notifications, but they're benign in that // this thread will eventually pick up the work item anyway, as will other threads when another // work item notification is received. wsq.LocalPush(task); if (m_threadsWaiting > 0) // OK to read lock-free. { lock (m_queue) { Monitor.Pulse(m_queue); } } } // Otherwise, add the work item to the global queue else { lock (m_queue) { m_queue.Enqueue(task); if (m_threadsWaiting > 0) { Monitor.Pulse(m_queue); } } } } }
/// <summary>Gets all of the tasks currently scheduled to this scheduler.</summary> /// <returns>An enumerable containing all of the scheduled tasks.</returns> protected override IEnumerable <Task> GetScheduledTasks() { // Keep track of all of the tasks we find List <Task> tasks = new List <Task>(); // Get all of the global tasks. We use TryEnter so as not to hang // a debugger if the lock is held by a frozen thread. bool lockTaken = false; try { Monitor.TryEnter(m_queue, ref lockTaken); if (lockTaken) { tasks.AddRange(m_queue.ToArray()); } else { throw new NotSupportedException(); } } finally { if (lockTaken) { Monitor.Exit(m_queue); } } // Now get all of the tasks from the work-stealing queues WorkStealingQueue <Task>[] queues = m_wsQueues; for (int i = 0; i < queues.Length; i++) { WorkStealingQueue <Task> wsq = queues[i]; if (wsq != null) { tasks.AddRange(wsq.ToArray()); } } // Return to the debugger all of the collected task instances return(tasks); }
/// <summary> /// The dispatch loop run by each thread in the scheduler. /// </summary> private void DispatchLoop() { // Create a new queue for this thread, store it in TLS for later retrieval, // and add it to the set of queues for this scheduler. WorkStealingQueue <Task> wsq = new WorkStealingQueue <Task>(); m_wsq = wsq; AddWsq(wsq); try { // Until there's no more work to do... while (true) { Task wi = null; // Search order: (1) local WSQ, (2) global Q, (3) steals from other queues. if (!wsq.LocalPop(ref wi)) { // We weren't able to get a task from the local WSQ bool searchedForSteals = false; while (true) { lock (m_queue) { // If shutdown was requested, exit the thread. if (m_shutdown) { return; } // (2) try the global queue. if (m_queue.Count != 0) { // We found a work item! Grab it ... wi = m_queue.Dequeue(); break; } else if (searchedForSteals) { // Note that we're not waiting for work, and then wait m_threadsWaiting++; try { Monitor.Wait(m_queue); } finally { m_threadsWaiting--; } // If we were signaled due to shutdown, exit the thread. if (m_shutdown) { return; } searchedForSteals = false; continue; } } // (3) try to steal. WorkStealingQueue <Task>[] wsQueues = m_wsQueues; int i; for (i = 0; i < wsQueues.Length; i++) { WorkStealingQueue <Task> q = wsQueues[i]; if (q != null && q != wsq && q.TrySteal(ref wi)) { break; } } if (i != wsQueues.Length) { break; } searchedForSteals = true; } } // ...and Invoke it. TryExecuteTask(wi); } } finally { RemoveWsq(wsq); } }