1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
5 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
9 // This file contains the primary interface and management of tasks and queues.
11 // =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
13 using System
.Collections
.Generic
;
14 using System
.Diagnostics
;
15 using System
.Runtime
.CompilerServices
;
17 namespace System
.Threading
.Tasks
20 /// Represents an abstract scheduler for tasks.
24 /// <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> acts as the extension point for all
25 /// pluggable scheduling logic. This includes mechanisms such as how to schedule a task for execution, and
26 /// how scheduled tasks should be exposed to debuggers.
29 /// All members of the abstract <see cref="TaskScheduler"/> type are thread-safe
30 /// and may be used from multiple threads concurrently.
33 [DebuggerDisplay("Id={Id}")]
34 [DebuggerTypeProxy(typeof(SystemThreadingTasks_TaskSchedulerDebugView
))]
35 public abstract class TaskScheduler
37 ////////////////////////////////////////////////////////////
39 // User Provided Methods and Properties
43 /// Queues a <see cref="System.Threading.Tasks.Task">Task</see> to the scheduler.
47 /// A class derived from <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>
48 /// implements this method to accept tasks being scheduled on the scheduler.
49 /// A typical implementation would store the task in an internal data structure, which would
50 /// be serviced by threads that would execute those tasks at some time in the future.
53 /// This method is only meant to be called by the .NET Framework and
54 /// should not be called directly by the derived class. This is necessary
55 /// for maintaining the consistency of the system.
58 /// <param name="task">The <see cref="System.Threading.Tasks.Task">Task</see> to be queued.</param>
59 /// <exception cref="System.ArgumentNullException">The <paramref name="task"/> argument is null.</exception>
60 protected internal abstract void QueueTask(Task task
);
63 /// Determines whether the provided <see cref="System.Threading.Tasks.Task">Task</see>
64 /// can be executed synchronously in this call, and if it can, executes it.
68 /// A class derived from <see cref="TaskScheduler">TaskScheduler</see> implements this function to
69 /// support inline execution of a task on a thread that initiates a wait on that task object. Inline
70 /// execution is optional, and the request may be rejected by returning false. However, better
71 /// scalability typically results the more tasks that can be inlined, and in fact a scheduler that
72 /// inlines too little may be prone to deadlocks. A proper implementation should ensure that a
73 /// request executing under the policies guaranteed by the scheduler can successfully inline. For
74 /// example, if a scheduler uses a dedicated thread to execute tasks, any inlining requests from that
75 /// thread should succeed.
78 /// If a scheduler decides to perform the inline execution, it should do so by calling to the base
80 /// <see cref="TryExecuteTask">TryExecuteTask</see> method with the provided task object, propagating
81 /// the return value. It may also be appropriate for the scheduler to remove an inlined task from its
82 /// internal data structures if it decides to honor the inlining request. Note, however, that under
83 /// some circumstances a scheduler may be asked to inline a task that was not previously provided to
84 /// it with the <see cref="QueueTask"/> method.
87 /// The derived scheduler is responsible for making sure that the calling thread is suitable for
88 /// executing the given task as far as its own scheduling and execution policies are concerned.
91 /// <param name="task">The <see cref="System.Threading.Tasks.Task">Task</see> to be
93 /// <param name="taskWasPreviouslyQueued">A Boolean denoting whether or not task has previously been
94 /// queued. If this parameter is True, then the task may have been previously queued (scheduled); if
95 /// False, then the task is known not to have been queued, and this call is being made in order to
96 /// execute the task inline without queueing it.</param>
97 /// <returns>A Boolean value indicating whether the task was executed inline.</returns>
98 /// <exception cref="System.ArgumentNullException">The <paramref name="task"/> argument is
100 /// <exception cref="System.InvalidOperationException">The <paramref name="task"/> was already
101 /// executed.</exception>
102 protected abstract bool TryExecuteTaskInline(Task task
, bool taskWasPreviouslyQueued
);
105 /// Generates an enumerable of <see cref="System.Threading.Tasks.Task">Task</see> instances
106 /// currently queued to the scheduler waiting to be executed.
110 /// A class derived from <see cref="TaskScheduler"/> implements this method in order to support
111 /// integration with debuggers. This method will only be invoked by the .NET Framework when the
112 /// debugger requests access to the data. The enumerable returned will be traversed by debugging
113 /// utilities to access the tasks currently queued to this scheduler, enabling the debugger to
114 /// provide a representation of this information in the user interface.
117 /// It is important to note that, when this method is called, all other threads in the process will
118 /// be frozen. Therefore, it's important to avoid synchronization with other threads that may lead to
119 /// blocking. If synchronization is necessary, the method should prefer to throw a <see
120 /// cref="System.NotSupportedException"/>
121 /// than to block, which could cause a debugger to experience delays. Additionally, this method and
122 /// the enumerable returned must not modify any globally visible state.
125 /// The returned enumerable should never be null. If there are currently no queued tasks, an empty
126 /// enumerable should be returned instead.
129 /// For developers implementing a custom debugger, this method shouldn't be called directly, but
130 /// rather this functionality should be accessed through the internal wrapper method
131 /// GetScheduledTasksForDebugger:
132 /// <c>internal Task[] GetScheduledTasksForDebugger()</c>. This method returns an array of tasks,
133 /// rather than an enumerable. In order to retrieve a list of active schedulers, a debugger may use
134 /// another internal method: <c>internal static TaskScheduler[] GetTaskSchedulersForDebugger()</c>.
135 /// This static method returns an array of all active TaskScheduler instances.
136 /// GetScheduledTasksForDebugger then may be used on each of these scheduler instances to retrieve
137 /// the list of scheduled tasks for each.
140 /// <returns>An enumerable that allows traversal of tasks currently queued to this scheduler.
142 /// <exception cref="System.NotSupportedException">
143 /// This scheduler is unable to generate a list of queued tasks at this time.
145 protected abstract IEnumerable
<Task
>? GetScheduledTasks();
148 /// Indicates the maximum concurrency level this
149 /// <see cref="TaskScheduler"/> is able to support.
151 public virtual int MaximumConcurrencyLevel
=> int.MaxValue
;
153 ////////////////////////////////////////////////////////////
155 // Internal overridable methods
160 /// Attempts to execute the target task synchronously.
162 /// <param name="task">The task to run.</param>
163 /// <param name="taskWasPreviouslyQueued">True if the task may have been previously queued,
164 /// false if the task was absolutely not previously queued.</param>
165 /// <returns>True if it ran, false otherwise.</returns>
166 internal bool TryRunInline(Task task
, bool taskWasPreviouslyQueued
)
168 // Do not inline unstarted tasks (i.e., task.ExecutingTaskScheduler == null).
169 // Do not inline TaskCompletionSource-style (a.k.a. "promise") tasks.
170 // No need to attempt inlining if the task body was already run (i.e. either TASK_STATE_DELEGATE_INVOKED or TASK_STATE_CANCELED bits set)
171 TaskScheduler
? ets
= task
.ExecutingTaskScheduler
;
173 // Delegate cross-scheduler inlining requests to target scheduler
174 if (ets
!= this && ets
!= null) return ets
.TryRunInline(task
, taskWasPreviouslyQueued
);
177 (task
.m_action
== null) ||
178 task
.IsDelegateInvoked
||
180 !RuntimeHelpers
.TryEnsureSufficientExecutionStack())
185 // Task class will still call into TaskScheduler.TryRunInline rather than TryExecuteTaskInline() so that
186 // 1) we can adjust the return code from TryExecuteTaskInline in case a buggy custom scheduler lies to us
187 // 2) we maintain a mechanism for the TLS lookup optimization that we used to have for the ConcRT scheduler (will potentially introduce the same for TP)
188 if (TplEventSource
.Log
.IsEnabled())
189 task
.FireTaskScheduledIfNeeded(this);
191 bool inlined
= TryExecuteTaskInline(task
, taskWasPreviouslyQueued
);
193 // If the custom scheduler returned true, we should either have the TASK_STATE_DELEGATE_INVOKED or TASK_STATE_CANCELED bit set
194 // Otherwise the scheduler is buggy
195 if (inlined
&& !(task
.IsDelegateInvoked
|| task
.IsCanceled
))
197 throw new InvalidOperationException(SR
.TaskScheduler_InconsistentStateAfterTryExecuteTaskInline
);
204 /// Attempts to dequeue a <see cref="System.Threading.Tasks.Task">Task</see> that was previously queued to
207 /// <param name="task">The <see cref="System.Threading.Tasks.Task">Task</see> to be dequeued.</param>
208 /// <returns>A Boolean denoting whether the <paramref name="task"/> argument was successfully dequeued.</returns>
209 /// <exception cref="System.ArgumentNullException">The <paramref name="task"/> argument is null.</exception>
210 protected internal virtual bool TryDequeue(Task task
)
216 /// Notifies the scheduler that a work item has made progress.
218 internal virtual void NotifyWorkItemProgress()
223 /// Indicates whether this is a custom scheduler, in which case the safe code paths will be taken upon task entry
224 /// using a CAS to transition from queued state to executing.
226 internal virtual bool RequiresAtomicStartTransition
=> true;
229 /// Calls QueueTask() after performing any needed firing of events
231 internal void InternalQueueTask(Task task
)
233 Debug
.Assert(task
!= null);
235 if (TplEventSource
.Log
.IsEnabled())
236 task
.FireTaskScheduledIfNeeded(this);
238 this.QueueTask(task
);
242 ////////////////////////////////////////////////////////////
247 // The global container that keeps track of TaskScheduler instances for debugging purposes.
248 private static ConditionalWeakTable
<TaskScheduler
, object?>? s_activeTaskSchedulers
;
250 // An AppDomain-wide default manager.
251 private static readonly TaskScheduler s_defaultTaskScheduler
= new ThreadPoolTaskScheduler();
253 // static counter used to generate unique TaskScheduler IDs
254 internal static int s_taskSchedulerIdCounter
;
256 // this TaskScheduler's unique ID
257 private volatile int m_taskSchedulerId
;
261 ////////////////////////////////////////////////////////////
263 // Constructors and public properties
267 /// Initializes the <see cref="System.Threading.Tasks.TaskScheduler"/>.
269 protected TaskScheduler()
271 #if CORECLR // Debugger support
272 // Register the scheduler in the active scheduler list. This is only relevant when debugging,
273 // so we only pay the cost if the debugger is attached when the scheduler is created. This
274 // means that the internal TaskScheduler.GetTaskSchedulersForDebugger() will only include
275 // schedulers created while the debugger is attached.
276 if (Debugger
.IsAttached
)
278 AddToActiveTaskSchedulers();
283 /// <summary>Adds this scheduler ot the active schedulers tracking collection for debugging purposes.</summary>
284 private void AddToActiveTaskSchedulers()
286 ConditionalWeakTable
<TaskScheduler
, object?>? activeTaskSchedulers
= s_activeTaskSchedulers
;
287 if (activeTaskSchedulers
== null)
289 Interlocked
.CompareExchange(ref s_activeTaskSchedulers
, new ConditionalWeakTable
<TaskScheduler
, object?>(), null);
290 activeTaskSchedulers
= s_activeTaskSchedulers
;
292 activeTaskSchedulers
.Add(this, null);
296 /// Gets the default <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> instance.
298 public static TaskScheduler Default
=> s_defaultTaskScheduler
;
301 /// Gets the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>
302 /// associated with the currently executing task.
305 /// When not called from within a task, <see cref="Current"/> will return the <see cref="Default"/> scheduler.
307 public static TaskScheduler Current
=> InternalCurrent
?? Default
;
310 /// Gets the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>
311 /// associated with the currently executing task.
314 /// When not called from within a task, <see cref="InternalCurrent"/> will return null.
316 internal static TaskScheduler
? InternalCurrent
320 Task
? currentTask
= Task
.InternalCurrent
;
321 return ((currentTask
!= null)
322 && ((currentTask
.CreationOptions
& TaskCreationOptions
.HideScheduler
) == 0)
323 ) ? currentTask
.ExecutingTaskScheduler
: null;
328 /// Creates a <see cref="TaskScheduler"/>
329 /// associated with the current <see cref="System.Threading.SynchronizationContext"/>.
332 /// All <see cref="System.Threading.Tasks.Task">Task</see> instances queued to
333 /// the returned scheduler will be executed through a call to the
334 /// <see cref="System.Threading.SynchronizationContext.Post">Post</see> method
338 /// A <see cref="TaskScheduler"/> associated with
339 /// the current <see cref="System.Threading.SynchronizationContext">SynchronizationContext</see>, as
340 /// determined by <see cref="System.Threading.SynchronizationContext.Current">SynchronizationContext.Current</see>.
342 /// <exception cref="System.InvalidOperationException">
343 /// The current SynchronizationContext may not be used as a TaskScheduler.
345 public static TaskScheduler
FromCurrentSynchronizationContext()
347 return new SynchronizationContextTaskScheduler();
351 /// Gets the unique ID for this <see cref="TaskScheduler"/>.
357 if (m_taskSchedulerId
== 0)
361 // We need to repeat if Interlocked.Increment wraps around and returns 0.
362 // Otherwise next time this scheduler's Id is queried it will get a new value
365 newId
= Interlocked
.Increment(ref s_taskSchedulerIdCounter
);
366 } while (newId
== 0);
368 Interlocked
.CompareExchange(ref m_taskSchedulerId
, newId
, 0);
371 return m_taskSchedulerId
;
376 /// Attempts to execute the provided <see cref="System.Threading.Tasks.Task">Task</see>
377 /// on this scheduler.
381 /// Scheduler implementations are provided with <see cref="System.Threading.Tasks.Task">Task</see>
382 /// instances to be executed through either the <see cref="QueueTask"/> method or the
383 /// <see cref="TryExecuteTaskInline"/> method. When the scheduler deems it appropriate to run the
384 /// provided task, <see cref="TryExecuteTask"/> should be used to do so. TryExecuteTask handles all
385 /// aspects of executing a task, including action invocation, exception handling, state management,
386 /// and lifecycle control.
389 /// <see cref="TryExecuteTask"/> must only be used for tasks provided to this scheduler by the .NET
390 /// Framework infrastructure. It should not be used to execute arbitrary tasks obtained through
391 /// custom mechanisms.
394 /// <param name="task">
395 /// A <see cref="System.Threading.Tasks.Task">Task</see> object to be executed.</param>
396 /// <exception cref="System.InvalidOperationException">
397 /// The <paramref name="task"/> is not associated with this scheduler.
399 /// <returns>A Boolean that is true if <paramref name="task"/> was successfully executed, false if it
400 /// was not. A common reason for execution failure is that the task had previously been executed or
401 /// is in the process of being executed by another thread.</returns>
402 protected bool TryExecuteTask(Task task
)
404 if (task
.ExecutingTaskScheduler
!= this)
406 throw new InvalidOperationException(SR
.TaskScheduler_ExecuteTask_WrongTaskScheduler
);
409 return task
.ExecuteEntry();
412 ////////////////////////////////////////////////////////////
418 /// Occurs when a faulted <see cref="System.Threading.Tasks.Task"/>'s unobserved exception is about to trigger exception escalation
419 /// policy, which, by default, would terminate the process.
422 /// This AppDomain-wide event provides a mechanism to prevent exception
423 /// escalation policy (which, by default, terminates the process) from triggering.
424 /// Each handler is passed a <see cref="System.Threading.Tasks.UnobservedTaskExceptionEventArgs"/>
425 /// instance, which may be used to examine the exception and to mark it as observed.
427 public static event EventHandler
<UnobservedTaskExceptionEventArgs
>? UnobservedTaskException
;
429 ////////////////////////////////////////////////////////////
434 // This is called by the TaskExceptionHolder finalizer.
435 internal static void PublishUnobservedTaskException(object sender
, UnobservedTaskExceptionEventArgs ueea
)
437 UnobservedTaskException
?.Invoke(sender
, ueea
);
441 /// Provides an array of all queued <see cref="System.Threading.Tasks.Task">Task</see> instances
442 /// for the debugger.
445 /// The returned array is populated through a call to <see cref="GetScheduledTasks"/>.
446 /// Note that this function is only meant to be invoked by a debugger remotely.
447 /// It should not be called by any other codepaths.
449 /// <returns>An array of <see cref="System.Threading.Tasks.Task">Task</see> instances.</returns>
450 /// <exception cref="System.NotSupportedException">
451 /// This scheduler is unable to generate a list of queued tasks at this time.
453 internal Task
[]? GetScheduledTasksForDebugger()
455 // this can throw InvalidOperationException indicating that they are unable to provide the info
456 // at the moment. We should let the debugger receive that exception so that it can indicate it in the UI
457 IEnumerable
<Task
>? activeTasksSource
= GetScheduledTasks();
459 if (activeTasksSource
== null)
462 // If it can be cast to an array, use it directly
463 if (!(activeTasksSource
is Task
[] activeTasksArray
))
465 activeTasksArray
= (new List
<Task
>(activeTasksSource
)).ToArray();
468 // touch all Task.Id fields so that the debugger doesn't need to do a lot of cross-proc calls to generate them
469 foreach (Task t
in activeTasksArray
)
474 return activeTasksArray
;
478 /// Provides an array of all active <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>
479 /// instances for the debugger.
482 /// This function is only meant to be invoked by a debugger remotely.
483 /// It should not be called by any other codepaths.
485 /// <returns>An array of <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> instances.</returns>
486 internal static TaskScheduler
[] GetTaskSchedulersForDebugger()
488 if (s_activeTaskSchedulers
== null)
490 // No schedulers were tracked. Just give back the default.
491 return new TaskScheduler
[] { s_defaultTaskScheduler }
;
494 List
<TaskScheduler
> schedulers
= new List
<TaskScheduler
>();
495 foreach (KeyValuePair
<TaskScheduler
, object?> item
in s_activeTaskSchedulers
)
497 schedulers
.Add(item
.Key
);
500 if (!schedulers
.Contains(s_defaultTaskScheduler
))
502 // Make sure the default is included, in case the debugger attached
503 // after it was created.
504 schedulers
.Add(s_defaultTaskScheduler
);
507 TaskScheduler
[] arr
= schedulers
.ToArray();
508 foreach (TaskScheduler scheduler
in arr
)
510 Debug
.Assert(scheduler
!= null, "Table returned an incorrect Count or CopyTo failed");
511 _
= scheduler
.Id
; // force Ids for debugger
517 /// Nested class that provides debugger view for TaskScheduler
519 internal sealed class SystemThreadingTasks_TaskSchedulerDebugView
521 private readonly TaskScheduler m_taskScheduler
;
522 public SystemThreadingTasks_TaskSchedulerDebugView(TaskScheduler scheduler
)
524 m_taskScheduler
= scheduler
;
527 // returns the scheduler's Id
528 public int Id
=> m_taskScheduler
.Id
;
530 // returns the scheduler's GetScheduledTasks
531 public IEnumerable
<Task
>? ScheduledTasks
=> m_taskScheduler
.GetScheduledTasks();
539 /// A TaskScheduler implementation that executes all tasks queued to it through a call to
540 /// <see cref="System.Threading.SynchronizationContext.Post"/> on the <see cref="System.Threading.SynchronizationContext"/>
541 /// that its associated with. The default constructor for this class binds to the current <see cref="System.Threading.SynchronizationContext"/>
543 internal sealed class SynchronizationContextTaskScheduler
: TaskScheduler
545 private readonly SynchronizationContext m_synchronizationContext
;
548 /// Constructs a SynchronizationContextTaskScheduler associated with <see cref="System.Threading.SynchronizationContext.Current"/>
550 /// <exception cref="System.InvalidOperationException">This constructor expects <see cref="System.Threading.SynchronizationContext.Current"/> to be set.</exception>
551 internal SynchronizationContextTaskScheduler()
553 m_synchronizationContext
= SynchronizationContext
.Current
??
554 // make sure we have a synccontext to work with
555 throw new InvalidOperationException(SR
.TaskScheduler_FromCurrentSynchronizationContext_NoCurrent
);
559 /// Implementation of <see cref="System.Threading.Tasks.TaskScheduler.QueueTask"/> for this scheduler class.
561 /// Simply posts the tasks to be executed on the associated <see cref="System.Threading.SynchronizationContext"/>.
563 /// <param name="task"></param>
564 protected internal override void QueueTask(Task task
)
566 m_synchronizationContext
.Post(s_postCallback
, (object)task
);
570 /// Implementation of <see cref="System.Threading.Tasks.TaskScheduler.TryExecuteTaskInline"/> for this scheduler class.
572 /// The task will be executed inline only if the call happens within
573 /// the associated <see cref="System.Threading.SynchronizationContext"/>.
575 /// <param name="task"></param>
576 /// <param name="taskWasPreviouslyQueued"></param>
577 protected override bool TryExecuteTaskInline(Task task
, bool taskWasPreviouslyQueued
)
579 if (SynchronizationContext
.Current
== m_synchronizationContext
)
581 return TryExecuteTask(task
);
589 protected override IEnumerable
<Task
>? GetScheduledTasks()
595 /// Implements the <see cref="System.Threading.Tasks.TaskScheduler.MaximumConcurrencyLevel"/> property for
596 /// this scheduler class.
598 /// By default it returns 1, because a <see cref="System.Threading.SynchronizationContext"/> based
599 /// scheduler only supports execution on a single thread.
601 public override int MaximumConcurrencyLevel
=> 1;
603 // preallocated SendOrPostCallback delegate
604 private static readonly SendOrPostCallback s_postCallback
= s
=>
606 Debug
.Assert(s
is Task
);
607 ((Task
)s
).ExecuteEntry(); // with double-execute check because SC could be buggy
612 /// Provides data for the event that is raised when a faulted <see cref="System.Threading.Tasks.Task"/>'s
613 /// exception goes unobserved.
616 /// The Exception property is used to examine the exception without marking it
617 /// as observed, whereas the <see cref="SetObserved"/> method is used to mark the exception
618 /// as observed. Marking the exception as observed prevents it from triggering exception escalation policy
619 /// which, by default, terminates the process.
621 public class UnobservedTaskExceptionEventArgs
: EventArgs
623 private readonly AggregateException
? m_exception
;
624 internal bool m_observed
= false;
627 /// Initializes a new instance of the <see cref="UnobservedTaskExceptionEventArgs"/> class
628 /// with the unobserved exception.
630 /// <param name="exception">The Exception that has gone unobserved.</param>
631 public UnobservedTaskExceptionEventArgs(AggregateException
? exception
) { m_exception = exception; }
634 /// Marks the <see cref="Exception"/> as "observed," thus preventing it
635 /// from triggering exception escalation policy which, by default, terminates the process.
637 public void SetObserved() { m_observed = true; }
640 /// Gets whether this exception has been marked as "observed."
642 public bool Observed
=> m_observed
;
645 /// The Exception that went unobserved.
647 public AggregateException
? Exception
=> m_exception
;