In situation 1, taska
and taskb
are in the same scope. taska
bites the dust and we leave this scope prematurely. What becomes of taskb
? To put it another way: mechanically, how does the Framework/OS know how to kill taskb
or otherwise do something with it?
In situation 2, we don't leave scope prematurely, so taskb
completes its mission as expected. No surprises here.
(The real problem at hand is working out the cleanest way to handle ctrl+c in a console app that spins off several tasks using a couple daisy-chained TPL dataflow ActionBlocks with MaxDegreeOfParallelism set to processor count on the second one. This was a minimal repro of the concept. What's the wisdom (or folly) of waiting (or not waiting) for all completions if the user wants to abort?)
int a = 0;
int b = 0;
int counta = 0;
int countb = 0;
Task taska = Task.Run(() =>
{
Random rng = new Random((int)DateTime.UtcNow.Ticks);
for (int i = 0; i < 100000000; i++)
{
a += rng.Next(0, 1000000) - 500000;
counta++;
if (counta > 100000) { throw new NotImplementedException(); }
}
});
Task taskb = Task.Run(() =>
{
Random rng = new Random((int)DateTime.UtcNow.Ticks);
for (int i = 0; i < 100000000; i++)
{
b += rng.Next(0, 1000000) - 500000;
countb++;
}
});
try
{
Task.WaitAll(new Task[] { taska }); // SITUATION 1
//Task.WaitAll(new Task[] { taska, taskb }); // SITUATION 2
}
catch (Exception)
{
Console.WriteLine(a);
Console.WriteLine(b);
Console.WriteLine(counta);
Console.WriteLine(countb);
}
Output:
-30873531
-477919899
100001
3678224
EDIT: This question is similar to the linked questions, but I feel like it would still be helpful for future searchers, because it is focused on (plausible but incorrect) perceptions about how scope affects (or doesn't affect) the outcome.