-
Notifications
You must be signed in to change notification settings - Fork 0
/
Program.cs
166 lines (144 loc) · 6.87 KB
/
Program.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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
using System.Text;
namespace testWait
{
/// <summary>
/// Class to record information on timing of tasks
/// </summary>
class Event
{
public int Id { get; set; }
public DateTime StartTime { get; set; }
public DateTime EndTime { get; set; }
public bool CancelledBeforeStart { get; set; } = false;
public DateTime? CancelTriggerTime { get; set; }
public DateTime? CancelExceptionTime { get; set; }
// Properties to make sense of the recorded times
public double TotalDuration { get => (EndTime - StartTime).TotalSeconds; }
public double TriggeredAfter { get => CancelTriggerTime != null ? (CancelTriggerTime.Value - StartTime).TotalSeconds : -1.0; }
public double ExceptionAfter { get => CancelExceptionTime != null ? (CancelExceptionTime.Value - StartTime).TotalSeconds : -1.0; }
public override string ToString() => $"{Id},{StartTime},{TotalDuration},{TriggeredAfter},{ExceptionAfter},{CancelledBeforeStart}";
public string ReportCancel(DateTime cancelTime)
{
var t = CancelTriggerTime != null ? (CancelTriggerTime.Value - cancelTime).TotalSeconds : -1.0;
return $"{Id} cancel delegate called {t}s after cts.Cancel() was called";
}
}
internal class Program
{
static async Task Main(string[] args)
{
//////////////////////////////////////////////////////
// RUN CONFIGURATION
// set induceIssue to true to observe excessive delay. Set to false to observe expected behaviors
bool induceIssue = true;
int taskDelayMs = 30000; // Task.DelayAsync for 20 seconds
//////////////////////////////////////////////////////
// Hold results from all calls. Outer Main has Id=-1
List<Event> Events = new List<Event>();
int taskCount = Environment.ProcessorCount << 1; // twice as many tasks as logical cores
if (induceIssue) taskCount = Environment.ProcessorCount << 5; // 2^5 as may tasks as cores
Console.WriteLine($"Starting {taskCount} tasks on {Environment.ProcessorCount} Logical Cores");
DateTime start = DateTime.Now;
DateTime end = start;
var te = new Event
{
Id = -1,
StartTime = start,
EndTime = end
};
// Add the event representing the entire "Main"
Events.Add(te);
using var cts = new CancellationTokenSource();
// record the time when we detected the token was triggered
cts.Token.Register(() => end = DateTime.Now);
// create an array of cancellable tasks
var tasks = (from i in Enumerable.Range(0, taskCount) select DoSomething(i, taskDelayMs, cts.Token)).ToArray();
// try with Task.Run to see if it makes a difference (it does now))
//var tasks = (from i in Enumerable.Range(0, taskCount) select Task.Run(async () => await DoSomething(i, taskDelayMs, cts.Token), cts.Token)).ToArray();
// CancelAfter is what we want, but let's call cancel, explicitly, to observe delays
//cts.CancelAfter(200);
// wait 100ms to trigger the cancellation so that we have a chance to enter into the Task.Delay(...) calls
DateTime cancelStart = DateTime.MinValue;
var triggerTask = Task.Run(async () =>
{
await Task.Delay(100);
cancelStart = DateTime.Now;
var triggerTime = (cancelStart - start).TotalSeconds;
Console.WriteLine($"Cancelling work after {triggerTime}s");
cts.Cancel();
DateTime cancelEnd = DateTime.Now;
triggerTime = (cancelEnd - start).TotalSeconds;
var cancelDuration = (cancelEnd - cancelStart).TotalSeconds;
// report time at which the token source finished the call to Cancel() (observe long delay)
Console.WriteLine($"After calling cancel: {triggerTime}s (ctr..Cancel() duration: {cancelDuration})");
te.CancelTriggerTime = DateTime.Now;
});
try
{
// use wait instead of when to pass the token into the WaitAll rather than relying on "DoSomething"
//Task.WaitAll(tasks, cts.Token);
Events.AddRange(await Task.WhenAll(tasks));
}
catch (OperationCanceledException oce)
{
// records the time when the exception threw the OperationCancelledException (if it is thrown)
te.CancelExceptionTime = DateTime.Now;
Console.WriteLine("Main Task Cancelled Exception");
}
te.EndTime = DateTime.Now;
var duration = (DateTime.Now - start).TotalSeconds;
var cancelAfter = (end - start).TotalSeconds;
await triggerTask;
//wait for them all to _actually_ finish
//Events.AddRange(await Task.WhenAll(tasks));
#region Build results String
var sb = new StringBuilder();
// sort the events by when the cancellation token was triggered
foreach (var e in Events.OrderBy(e => e.TriggeredAfter).ToList())
{
//sb.AppendLine(e.ToString());
sb.AppendLine(e.ReportCancel(cancelStart));
}
#endregion
// Write out all of the results
Console.Write(sb.ToString());
Console.WriteLine($"MainTask, taskDuration: {duration}, cancelAfter: {cancelAfter}");
Console.WriteLine("Done processing. Press any key");
Console.ReadKey();
}
static async Task<Event> DoSomething(int i, int delayMs, CancellationToken token)
{
Event e = new();
//lock (Events) Events.Add(e);
try
{
e.Id = i;
//lock(log) log.AppendLine($"{i} started");
e.StartTime = DateTime.Now;
e.EndTime = e.StartTime;
// record the time when we detected the token was triggered
token.Register(() => e.CancelTriggerTime = DateTime.Now);
if (token.IsCancellationRequested)
{
e.CancelledBeforeStart = true;
return e;
}
try
{
await Task.Delay(delayMs, token);
}
catch (TaskCanceledException tce)
{
e.CancelExceptionTime = DateTime.Now;
}
e.EndTime = DateTime.Now;
//await Task.Delay(20);
return e;
}
finally
{
e.EndTime = DateTime.Now;
}
}
}
}