/
ThreadPoolTaskScheduler.cs
124 lines (111 loc) · 4.51 KB
/
ThreadPoolTaskScheduler.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
//
// TaskScheduler.cs
//
//
// This file contains the primary interface and management of tasks and queues.
//
// =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
using System.Collections.Generic;
using System.Diagnostics;
namespace System.Threading.Tasks
{
/// <summary>
/// An implementation of TaskScheduler that uses the ThreadPool scheduler
/// </summary>
internal sealed class ThreadPoolTaskScheduler : TaskScheduler
{
/// <summary>
/// Constructs a new ThreadPool task scheduler object
/// </summary>
internal ThreadPoolTaskScheduler()
{
_ = Id; // force ID creation of the default scheduler
}
// static delegate for threads allocated to handle LongRunning tasks.
private static readonly ParameterizedThreadStart s_longRunningThreadWork = static s =>
{
Debug.Assert(s is Task);
((Task)s).ExecuteEntryUnsafe(threadPoolThread: null);
};
/// <summary>
/// Schedules a task to the ThreadPool.
/// </summary>
/// <param name="task">The task to schedule.</param>
protected internal override void QueueTask(Task task)
{
TaskCreationOptions options = task.Options;
if (Thread.IsThreadStartSupported && (options & TaskCreationOptions.LongRunning) != 0)
{
// Run LongRunning tasks on their own dedicated thread.
new Thread(s_longRunningThreadWork)
{
IsBackground = true,
Name = ".NET Long Running Task"
}.UnsafeStart(task);
}
else
{
// Normal handling for non-LongRunning tasks.
ThreadPool.UnsafeQueueUserWorkItemInternal(task, (options & TaskCreationOptions.PreferFairness) == 0);
}
}
/// <summary>
/// This internal function will do this:
/// (1) If the task had previously been queued, attempt to pop it and return false if that fails.
/// (2) Return whether the task is executed
///
/// IMPORTANT NOTE: TryExecuteTaskInline will NOT throw task exceptions itself. Any wait code path using this function needs
/// to account for exceptions that need to be propagated, and throw themselves accordingly.
/// </summary>
protected override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued)
{
// If the task was previously scheduled, and we can't pop it, then return false.
if (taskWasPreviouslyQueued && !ThreadPool.TryPopCustomWorkItem(task))
return false;
try
{
task.ExecuteEntryUnsafe(threadPoolThread: null); // handles switching Task.Current etc.
}
finally
{
// Only call NWIP() if task was previously queued
if (taskWasPreviouslyQueued) NotifyWorkItemProgress();
}
return true;
}
protected internal override bool TryDequeue(Task task)
{
// just delegate to TP
return ThreadPool.TryPopCustomWorkItem(task);
}
protected override IEnumerable<Task> GetScheduledTasks()
{
return FilterTasksFromWorkItems(ThreadPool.GetQueuedWorkItems());
}
private static IEnumerable<Task> FilterTasksFromWorkItems(IEnumerable<object> tpwItems)
{
foreach (object tpwi in tpwItems)
{
if (tpwi is Task t)
{
yield return t;
}
}
}
/// <summary>
/// Notifies the scheduler that work is progressing (no-op).
/// </summary>
internal override void NotifyWorkItemProgress()
{
ThreadPool.NotifyWorkItemProgress();
}
/// <summary>
/// This is the only scheduler that returns false for this property, indicating that the task entry codepath is unsafe (CAS free)
/// since we know that the underlying scheduler already takes care of atomic transitions from queued to non-queued.
/// </summary>
internal override bool RequiresAtomicStartTransition => false;
}
}