Skip to content

Commit 3488b02

Browse files
authored
Improve cancel1.cs how-to example (#41321)
* Improve cancel1.cs how-to example I attempted to fix #27830, but as per my comment there, I don't think the original raised concern is correct. I did make some improvements though, which are present in this changeset I am submitting. This snippet is being used in "How to: Cancel a Task and Its Children" https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-cancel-a-task-and-its-children The mixed four-fold goals of the how-to make it a complex mixed bag of an example. * For distinction, label tasks as cancellable, parent, and child - in output and variable names * Use output prefixes to indicate in which context the message is logged * Reduce wait spins (it took infeasibly long on my PC) * Reduce parent spawn-loop-spin-waits so spawning happens faster than child task execution * Add "ran to completion" messages so execution can be followed and user cancel can be timed and experimented with at runtime * Use CancelAsync instead of Cancel on tokenSource * Revert cancel to syncr `Cancel()`
1 parent 75da548 commit 3488b02

File tree

1 file changed

+79
-65
lines changed
  • samples/snippets/csharp/VS_Snippets_Misc/tpl_cancellation/cs

1 file changed

+79
-65
lines changed

samples/snippets/csharp/VS_Snippets_Misc/tpl_cancellation/cs/cancel1.cs

Lines changed: 79 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -8,55 +8,61 @@ public class Example
88
{
99
public static async Task Main()
1010
{
11-
var tokenSource = new CancellationTokenSource();
11+
// Cancellation token source for cancellation. Make sure to dispose after use (which is done here through the using expression).
12+
using var tokenSource = new CancellationTokenSource();
13+
14+
// The cancellation token will be used to communicate cancellation to tasks
1215
var token = tokenSource.Token;
1316

17+
Console.WriteLine("Main: Press any key to begin tasks...");
18+
Console.ReadKey(true);
19+
Console.WriteLine("Main: To terminate the example, press 'c' to cancel and exit...");
20+
Console.WriteLine();
21+
1422
// Store references to the tasks so that we can wait on them and
1523
// observe their status after cancellation.
16-
Task t;
1724
var tasks = new ConcurrentBag<Task>();
1825

19-
Console.WriteLine("Press any key to begin tasks...");
20-
Console.ReadKey(true);
21-
Console.WriteLine("To terminate the example, press 'c' to cancel and exit...");
22-
Console.WriteLine();
26+
// Pass the token to the user delegate so it can cancel during execution,
27+
// and also to the task so it can cancel before execution starts.
28+
var cancellableTask = Task.Run(() => {
29+
DoSomeWork(token);
30+
Console.WriteLine("Cancellable: Task {0} ran to completion", Task.CurrentId);
31+
}, token);
32+
Console.WriteLine("Main: Cancellable Task {0} created", cancellableTask.Id);
33+
tasks.Add(cancellableTask);
2334

24-
// Request cancellation of a single task when the token source is canceled.
25-
// Pass the token to the user delegate, and also to the task so it can
26-
// handle the exception correctly.
27-
t = Task.Run(() => DoSomeWork(token), token);
28-
Console.WriteLine("Task {0} executing", t.Id);
29-
tasks.Add(t);
30-
31-
// Request cancellation of a task and its children. Note the token is passed
32-
// to (1) the user delegate and (2) as the second argument to Task.Run, so
33-
// that the task instance can correctly handle the OperationCanceledException.
34-
t = Task.Run(() =>
35+
var parentTask = Task.Run(() =>
3536
{
36-
// Create some cancelable child tasks.
37-
Task tc;
38-
for (int i = 3; i <= 10; i++)
37+
for (int i = 0; i <= 7; i++)
3938
{
39+
// If cancellation was requested we don't need to start any more
40+
// child tasks (that would immediately cancel) => break out of loop
41+
if (token.IsCancellationRequested) break;
42+
4043
// For each child task, pass the same token
4144
// to each user delegate and to Task.Run.
42-
tc = Task.Run(() => DoSomeWork(token), token);
43-
Console.WriteLine("Task {0} executing", tc.Id);
44-
tasks.Add(tc);
45-
// Pass the same token again to do work on the parent task.
46-
// All will be signaled by the call to tokenSource.Cancel below.
47-
DoSomeWork(token);
45+
var childTask = Task.Run(() => {
46+
DoSomeWork(token);
47+
Console.WriteLine("Child: Task {0} ran to completion", Task.CurrentId);
48+
}, token);
49+
Console.WriteLine("Parent: Task {0} created", childTask.Id);
50+
tasks.Add(childTask);
51+
52+
DoSomeWork(token, maxIterations: 1);
4853
}
49-
}, token);
5054

51-
Console.WriteLine("Task {0} executing", t.Id);
52-
tasks.Add(t);
55+
Console.WriteLine("Parent: Task {0} ran to completion", Task.CurrentId);
56+
}, token);
57+
Console.WriteLine("Main: Parent Task {0} created", parentTask.Id);
58+
tasks.Add(parentTask);
5359

5460
// Request cancellation from the UI thread.
5561
char ch = Console.ReadKey().KeyChar;
5662
if (ch == 'c' || ch == 'C')
5763
{
5864
tokenSource.Cancel();
59-
Console.WriteLine("\nTask cancellation requested.");
65+
Console.WriteLine("\nMain: Task cancellation requested.");
6066

6167
// Optional: Observe the change in the Status property on the task.
6268
// It is not necessary to wait on tasks that have canceled. However,
@@ -68,34 +74,30 @@ public static async Task Main()
6874

6975
try
7076
{
71-
await Task.WhenAll(tasks.ToArray());
77+
// Wait for all tasks before disposing the cancellation token source
78+
await Task.WhenAll(tasks);
7279
}
7380
catch (OperationCanceledException)
7481
{
75-
Console.WriteLine($"\n{nameof(OperationCanceledException)} thrown\n");
76-
}
77-
finally
78-
{
79-
tokenSource.Dispose();
82+
Console.WriteLine($"\nMain: {nameof(OperationCanceledException)} thrown\n");
8083
}
8184

8285
// Display status of all tasks.
8386
foreach (var task in tasks)
84-
Console.WriteLine("Task {0} status is now {1}", task.Id, task.Status);
87+
{
88+
Console.WriteLine("Main: Task {0} status is now {1}", task.Id, task.Status);
89+
}
8590
}
8691

87-
static void DoSomeWork(CancellationToken ct)
92+
static void DoSomeWork(CancellationToken ct, int maxIterations = 10)
8893
{
8994
// Was cancellation already requested?
9095
if (ct.IsCancellationRequested)
9196
{
92-
Console.WriteLine("Task {0} was cancelled before it got started.",
93-
Task.CurrentId);
97+
Console.WriteLine("Task {0} was cancelled before it got started.", Task.CurrentId);
9498
ct.ThrowIfCancellationRequested();
9599
}
96100

97-
int maxIterations = 100;
98-
99101
// NOTE!!! A "TaskCanceledException was unhandled
100102
// by user code" error will be raised here if "Just My Code"
101103
// is enabled on your computer. On Express editions JMC is
@@ -110,37 +112,49 @@ static void DoSomeWork(CancellationToken ct)
110112

111113
if (ct.IsCancellationRequested)
112114
{
113-
Console.WriteLine("Task {0} cancelled", Task.CurrentId);
115+
Console.WriteLine("Task {0} work cancelled", Task.CurrentId);
114116
ct.ThrowIfCancellationRequested();
115117
}
116118
}
117119
}
118120
}
119121
// The example displays output like the following:
120-
// Press any key to begin tasks...
121-
// To terminate the example, press 'c' to cancel and exit...
122+
// Main: Press any key to begin tasks...
123+
// Main: To terminate the example, press 'c' to cancel and exit...
122124
//
123-
// Task 1 executing
124-
// Task 2 executing
125-
// Task 3 executing
126-
// Task 4 executing
127-
// Task 5 executing
128-
// Task 6 executing
129-
// Task 7 executing
130-
// Task 8 executing
125+
// Main: Cancellable Task 13 created
126+
// Main: Parent Task 14 created
127+
// Parent: Task 15 created
128+
// Parent: Task 16 created
129+
// Parent: Task 17 created
130+
// Parent: Task 18 created
131+
// Parent: Task 19 created
132+
// Parent: Task 20 created
133+
// Cancellable: Task 13 ran to completion
134+
// Child: Task 15 ran to completion
135+
// Parent: Task 21 created
136+
// Child: Task 16 ran to completion
137+
// Parent: Task 22 created
138+
// Child: Task 17 ran to completion
131139
// c
132-
// Task cancellation requested.
133-
// Task 2 cancelled
134-
// Task 7 cancelled
140+
// Main: Task cancellation requested.
141+
// Task 20 work cancelled
142+
// Task 21 work cancelled
143+
// Task 22 work cancelled
144+
// Task 18 work cancelled
145+
// Task 14 work cancelled
146+
// Task 19 work cancelled
135147
//
136-
// OperationCanceledException thrown
148+
// Main: OperationCanceledException thrown
137149
//
138-
// Task 2 status is now Canceled
139-
// Task 1 status is now RanToCompletion
140-
// Task 8 status is now Canceled
141-
// Task 7 status is now Canceled
142-
// Task 6 status is now RanToCompletion
143-
// Task 5 status is now RanToCompletion
144-
// Task 4 status is now RanToCompletion
145-
// Task 3 status is now RanToCompletion
150+
// Main: Task 22 status is now Canceled
151+
// Main: Task 21 status is now Canceled
152+
// Main: Task 20 status is now Canceled
153+
// Main: Task 19 status is now Canceled
154+
// Main: Task 18 status is now Canceled
155+
// Main: Task 17 status is now RanToCompletion
156+
// Main: Task 16 status is now RanToCompletion
157+
// Main: Task 15 status is now RanToCompletion
158+
// Main: Task 14 status is now Canceled
159+
// Main: Task 13 status is now RanToCompletion
146160
// </Snippet04>

0 commit comments

Comments
 (0)