/// <summary>The body of the async processor to be run in a Task. Only one should be running at a time.</summary> /// <remarks>This has been separated out into its own method to improve the Parallel Tasks window experience.</remarks> private void ConcurrentExclusiveInterleaveProcessor() { // Run while there are more tasks to be processed. We assume that the first time through, // there are tasks. If they aren't, worst case is we try to process and find none. bool runTasks = true; bool cleanupOnExit = true; while (runTasks) { try { // Process all waiting exclusive tasks foreach (var task in GetExclusiveTasks()) { _exclusiveTaskScheduler.ExecuteTask(task); // Just because we executed the task doesn't mean it's "complete", // if it has child tasks that have not yet completed // and will complete later asynchronously. To account for this, // if a task isn't yet completed, leave the interleave processor // but leave it still in a running state. When the task completes, // we'll come back in and keep going. Note that the children // must not be scheduled to this interleave, or this will deadlock. if (_exclusiveProcessingIncludesChildren && !task.IsCompleted) { cleanupOnExit = false; task.ContinueWith(_ => ConcurrentExclusiveInterleaveProcessor(), _parallelOptions.TaskScheduler); return; } } // Process all waiting concurrent tasks *until* any exclusive tasks show up, in which // case we want to switch over to processing those (by looping around again). System.Threading.Tasks.Parallel.ForEach(GetConcurrentTasksUntilExclusiveExists(), _parallelOptions, ExecuteConcurrentTask); } finally { if (cleanupOnExit) { lock (_internalLock) { // If there are no tasks remaining, we're done. If there are, loop around and go again. if (_concurrentTaskScheduler.Tasks.Count == 0 && _exclusiveTaskScheduler.Tasks.Count == 0) { _taskExecuting = null; runTasks = false; } } } } } }
/// <summary> /// Processes exclusive tasks serially until either there are no more to process /// or we've reached our user-specified maximum limit. /// </summary> private void ProcessExclusiveTasks() { Contract.Requires(m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL, "Processing exclusive tasks requires being in exclusive mode."); Contract.Requires(!m_exclusiveTaskScheduler.m_tasks.IsEmpty, "Processing exclusive tasks requires tasks to be processed."); ContractAssertMonitorStatus(ValueLock, held: false); try { // Note that we're processing exclusive tasks on the current thread Contract.Assert(!m_threadProcessingMapping.ContainsKey(Thread.CurrentThread.ManagedThreadId), "This thread should not yet be involved in this pair's processing."); m_threadProcessingMapping[Thread.CurrentThread.ManagedThreadId] = ProcessingMode.ProcessingExclusiveTask; // Process up to the maximum number of items per task allowed for (int i = 0; i < m_maxItemsPerTask; i++) { // Get the next available exclusive task. If we can't find one, bail. Task exclusiveTask; if (!m_exclusiveTaskScheduler.m_tasks.TryDequeue(out exclusiveTask)) { break; } // Execute the task. If the scheduler was previously faulted, // this task could have been faulted when it was queued; ignore such tasks. if (!exclusiveTask.IsFaulted) { m_exclusiveTaskScheduler.ExecuteTask(exclusiveTask); } } } finally { // We're no longer processing exclusive tasks on the current thread ProcessingMode currentMode; m_threadProcessingMapping.TryRemove(Thread.CurrentThread.ManagedThreadId, out currentMode); Contract.Assert(currentMode == ProcessingMode.ProcessingExclusiveTask, "Somehow we ended up escaping exclusive mode."); lock (ValueLock) { // When this task was launched, we tracked it by setting m_processingCount to WRITER_IN_PROGRESS. // now reset it to 0. Then check to see whether there's more processing to be done. // There might be more concurrent tasks available, for example, if concurrent tasks arrived // after we exited the loop, or if we exited the loop while concurrent tasks were still // available but we hit our maxItemsPerTask limit. Contract.Assert(m_processingCount == EXCLUSIVE_PROCESSING_SENTINEL, "The processing mode should not have deviated from exclusive."); m_processingCount = 0; ProcessAsyncIfNecessary(true); } } }
/// <summary> /// Processes concurrent tasks serially until either there are no more to process, /// we've reached our user-specified maximum limit, or exclusive tasks have arrived. /// </summary> private void ProcessConcurrentTasks() { Contract.Requires(m_processingCount > 0, "Processing concurrent tasks requires us to be in concurrent mode."); ContractAssertMonitorStatus(ValueLock, held: false); try { // Note that we're processing concurrent tasks on the current thread Contract.Assert(!m_threadProcessingMapping.ContainsKey(Thread.CurrentThread.ManagedThreadId), "This thread should not yet be involved in this pair's processing."); m_threadProcessingMapping[Thread.CurrentThread.ManagedThreadId] = ProcessingMode.ProcessingConcurrentTasks; // Process up to the maximum number of items per task allowed for (int i = 0; i < m_maxItemsPerTask; i++) { // Get the next available concurrent task. If we can't find one, bail. Task concurrentTask; if (!m_concurrentTaskScheduler.m_tasks.TryDequeue(out concurrentTask)) { break; } // Execute the task. If the scheduler was previously faulted, // this task could have been faulted when it was queued; ignore such tasks. if (!concurrentTask.IsFaulted) { m_concurrentTaskScheduler.ExecuteTask(concurrentTask); } // Now check to see if exclusive tasks have arrived; if any have, they take priority // so we'll bail out here. Note that we could have checked this condition // in the for loop's condition, but that could lead to extra overhead // in the case where a concurrent task arrives, this task is launched, and then // before entering the loop an exclusive task arrives. If we didn't execute at // least one task, we would have spent all of the overhead to launch a // task but with none of the benefit. There's of course also an inherent // race condition here with regards to exclusive tasks arriving, and we're ok with // executing one more concurrent task than we should before giving priority to exclusive tasks. if (!m_exclusiveTaskScheduler.m_tasks.IsEmpty) { break; } } } finally { // We're no longer processing concurrent tasks on the current thread ProcessingMode currentMode; m_threadProcessingMapping.TryRemove(Thread.CurrentThread.ManagedThreadId, out currentMode); Contract.Assert(currentMode == ProcessingMode.ProcessingConcurrentTasks, "Somehow we ended up escaping concurrent mode."); lock (ValueLock) { // When this task was launched, we tracked it with a positive processing count; // decrement that count. Then check to see whether there's more processing to be done. // There might be more concurrent tasks available, for example, if concurrent tasks arrived // after we exited the loop, or if we exited the loop while concurrent tasks were still // available but we hit our maxItemsPerTask limit. Contract.Assert(m_processingCount > 0, "The procesing mode should not have deviated from concurrent."); if (m_processingCount > 0) { --m_processingCount; } ProcessAsyncIfNecessary(true); } } }
/// <summary>Runs a concurrent task.</summary> /// <param name="task">The task to execute.</param> /// <remarks>This has been separated out into its own method to improve the Parallel Tasks window experience.</remarks> private void ExecuteConcurrentTask(Task task) { _concurrentTaskScheduler.ExecuteTask(task); }