More corelib cleanup (dotnet/coreclr#26993)
[mono-project.git] / netcore / System.Private.CoreLib / shared / System / Threading / Tasks / TaskScheduler.cs
blob545d22410bd19333bbf66bb2e017f249ebecc753
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 // =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6 //
7 //
8 //
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
19 /// <summary>
20 /// Represents an abstract scheduler for tasks.
21 /// </summary>
22 /// <remarks>
23 /// <para>
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.
27 /// </para>
28 /// <para>
29 /// All members of the abstract <see cref="TaskScheduler"/> type are thread-safe
30 /// and may be used from multiple threads concurrently.
31 /// </para>
32 /// </remarks>
33 [DebuggerDisplay("Id={Id}")]
34 [DebuggerTypeProxy(typeof(SystemThreadingTasks_TaskSchedulerDebugView))]
35 public abstract class TaskScheduler
37 ////////////////////////////////////////////////////////////
39 // User Provided Methods and Properties
42 /// <summary>
43 /// Queues a <see cref="System.Threading.Tasks.Task">Task</see> to the scheduler.
44 /// </summary>
45 /// <remarks>
46 /// <para>
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.
51 /// </para>
52 /// <para>
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.
56 /// </para>
57 /// </remarks>
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);
62 /// <summary>
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.
65 /// </summary>
66 /// <remarks>
67 /// <para>
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.
76 /// </para>
77 /// <para>
78 /// If a scheduler decides to perform the inline execution, it should do so by calling to the base
79 /// TaskScheduler's
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.
85 /// </para>
86 /// <para>
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.
89 /// </para>
90 /// </remarks>
91 /// <param name="task">The <see cref="System.Threading.Tasks.Task">Task</see> to be
92 /// executed.</param>
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
99 /// null.</exception>
100 /// <exception cref="System.InvalidOperationException">The <paramref name="task"/> was already
101 /// executed.</exception>
102 protected abstract bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued);
104 /// <summary>
105 /// Generates an enumerable of <see cref="System.Threading.Tasks.Task">Task</see> instances
106 /// currently queued to the scheduler waiting to be executed.
107 /// </summary>
108 /// <remarks>
109 /// <para>
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.
115 /// </para>
116 /// <para>
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.
123 /// </para>
124 /// <para>
125 /// The returned enumerable should never be null. If there are currently no queued tasks, an empty
126 /// enumerable should be returned instead.
127 /// </para>
128 /// <para>
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.
138 /// </para>
139 /// </remarks>
140 /// <returns>An enumerable that allows traversal of tasks currently queued to this scheduler.
141 /// </returns>
142 /// <exception cref="System.NotSupportedException">
143 /// This scheduler is unable to generate a list of queued tasks at this time.
144 /// </exception>
145 protected abstract IEnumerable<Task>? GetScheduledTasks();
147 /// <summary>
148 /// Indicates the maximum concurrency level this
149 /// <see cref="TaskScheduler"/> is able to support.
150 /// </summary>
151 public virtual int MaximumConcurrencyLevel => int.MaxValue;
153 ////////////////////////////////////////////////////////////
155 // Internal overridable methods
159 /// <summary>
160 /// Attempts to execute the target task synchronously.
161 /// </summary>
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);
176 if ((ets == null) ||
177 (task.m_action == null) ||
178 task.IsDelegateInvoked ||
179 task.IsCanceled ||
180 !RuntimeHelpers.TryEnsureSufficientExecutionStack())
182 return false;
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);
200 return inlined;
203 /// <summary>
204 /// Attempts to dequeue a <see cref="System.Threading.Tasks.Task">Task</see> that was previously queued to
205 /// this scheduler.
206 /// </summary>
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)
212 return false;
215 /// <summary>
216 /// Notifies the scheduler that a work item has made progress.
217 /// </summary>
218 internal virtual void NotifyWorkItemProgress()
222 /// <summary>
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.
225 /// </summary>
226 internal virtual bool RequiresAtomicStartTransition => true;
228 /// <summary>
229 /// Calls QueueTask() after performing any needed firing of events
230 /// </summary>
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 ////////////////////////////////////////////////////////////
244 // Member variables
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
266 /// <summary>
267 /// Initializes the <see cref="System.Threading.Tasks.TaskScheduler"/>.
268 /// </summary>
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();
280 #endif
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);
295 /// <summary>
296 /// Gets the default <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see> instance.
297 /// </summary>
298 public static TaskScheduler Default => s_defaultTaskScheduler;
300 /// <summary>
301 /// Gets the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>
302 /// associated with the currently executing task.
303 /// </summary>
304 /// <remarks>
305 /// When not called from within a task, <see cref="Current"/> will return the <see cref="Default"/> scheduler.
306 /// </remarks>
307 public static TaskScheduler Current => InternalCurrent ?? Default;
309 /// <summary>
310 /// Gets the <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>
311 /// associated with the currently executing task.
312 /// </summary>
313 /// <remarks>
314 /// When not called from within a task, <see cref="InternalCurrent"/> will return null.
315 /// </remarks>
316 internal static TaskScheduler? InternalCurrent
320 Task? currentTask = Task.InternalCurrent;
321 return ((currentTask != null)
322 && ((currentTask.CreationOptions & TaskCreationOptions.HideScheduler) == 0)
323 ) ? currentTask.ExecutingTaskScheduler : null;
327 /// <summary>
328 /// Creates a <see cref="TaskScheduler"/>
329 /// associated with the current <see cref="System.Threading.SynchronizationContext"/>.
330 /// </summary>
331 /// <remarks>
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
335 /// on that context.
336 /// </remarks>
337 /// <returns>
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>.
341 /// </returns>
342 /// <exception cref="System.InvalidOperationException">
343 /// The current SynchronizationContext may not be used as a TaskScheduler.
344 /// </exception>
345 public static TaskScheduler FromCurrentSynchronizationContext()
347 return new SynchronizationContextTaskScheduler();
350 /// <summary>
351 /// Gets the unique ID for this <see cref="TaskScheduler"/>.
352 /// </summary>
353 public int Id
357 if (m_taskSchedulerId == 0)
359 int newId;
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;
375 /// <summary>
376 /// Attempts to execute the provided <see cref="System.Threading.Tasks.Task">Task</see>
377 /// on this scheduler.
378 /// </summary>
379 /// <remarks>
380 /// <para>
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.
387 /// </para>
388 /// <para>
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.
392 /// </para>
393 /// </remarks>
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.
398 /// </exception>
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 ////////////////////////////////////////////////////////////
414 // Events
417 /// <summary>
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.
420 /// </summary>
421 /// <remarks>
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.
426 /// </remarks>
427 public static event EventHandler<UnobservedTaskExceptionEventArgs>? UnobservedTaskException;
429 ////////////////////////////////////////////////////////////
431 // Internal methods
434 // This is called by the TaskExceptionHolder finalizer.
435 internal static void PublishUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs ueea)
437 UnobservedTaskException?.Invoke(sender, ueea);
440 /// <summary>
441 /// Provides an array of all queued <see cref="System.Threading.Tasks.Task">Task</see> instances
442 /// for the debugger.
443 /// </summary>
444 /// <remarks>
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.
448 /// </remarks>
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.
452 /// </exception>
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)
460 return 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)
471 _ = t.Id;
474 return activeTasksArray;
477 /// <summary>
478 /// Provides an array of all active <see cref="System.Threading.Tasks.TaskScheduler">TaskScheduler</see>
479 /// instances for the debugger.
480 /// </summary>
481 /// <remarks>
482 /// This function is only meant to be invoked by a debugger remotely.
483 /// It should not be called by any other codepaths.
484 /// </remarks>
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
513 return arr;
516 /// <summary>
517 /// Nested class that provides debugger view for TaskScheduler
518 /// </summary>
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();
538 /// <summary>
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"/>
542 /// </summary>
543 internal sealed class SynchronizationContextTaskScheduler : TaskScheduler
545 private readonly SynchronizationContext m_synchronizationContext;
547 /// <summary>
548 /// Constructs a SynchronizationContextTaskScheduler associated with <see cref="System.Threading.SynchronizationContext.Current"/>
549 /// </summary>
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);
558 /// <summary>
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"/>.
562 /// </summary>
563 /// <param name="task"></param>
564 protected internal override void QueueTask(Task task)
566 m_synchronizationContext.Post(s_postCallback, (object)task);
569 /// <summary>
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"/>.
574 /// </summary>
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);
583 else
585 return false;
589 protected override IEnumerable<Task>? GetScheduledTasks()
591 return null;
594 /// <summary>
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.
600 /// </summary>
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
611 /// <summary>
612 /// Provides data for the event that is raised when a faulted <see cref="System.Threading.Tasks.Task"/>'s
613 /// exception goes unobserved.
614 /// </summary>
615 /// <remarks>
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.
620 /// </remarks>
621 public class UnobservedTaskExceptionEventArgs : EventArgs
623 private readonly AggregateException? m_exception;
624 internal bool m_observed = false;
626 /// <summary>
627 /// Initializes a new instance of the <see cref="UnobservedTaskExceptionEventArgs"/> class
628 /// with the unobserved exception.
629 /// </summary>
630 /// <param name="exception">The Exception that has gone unobserved.</param>
631 public UnobservedTaskExceptionEventArgs(AggregateException? exception) { m_exception = exception; }
633 /// <summary>
634 /// Marks the <see cref="Exception"/> as "observed," thus preventing it
635 /// from triggering exception escalation policy which, by default, terminates the process.
636 /// </summary>
637 public void SetObserved() { m_observed = true; }
639 /// <summary>
640 /// Gets whether this exception has been marked as "observed."
641 /// </summary>
642 public bool Observed => m_observed;
644 /// <summary>
645 /// The Exception that went unobserved.
646 /// </summary>
647 public AggregateException? Exception => m_exception;