Your example is very interesting - it shows the side effects of parallel processing. To answer your question, and to make it easier to see the side effects, I've slightly modified your example:
using System;
using System.Threading;
using System.Diagnostics;
public class Program
{
public static void Main()
{
(new Example()).Main();
}
}
public class Example
{
public void Main()
{
System.Timers.Timer t = new System.Timers.Timer(10);
t.Enabled = true;
t.Elapsed += (sender, args) => c();
Console.ReadLine(); t.Enabled = false;
}
int t = 0;
int h = 0;
public void c()
{
h++;
new Thread(() => doWork(h)).Start();
}
public void doWork(int h2)
{
Stopwatch sw = new Stopwatch();
sw.Start();
try
{
t++;
Console.WriteLine("h={0}, h2={1}, threads={2} [start]", h, h2, t);
Thread.Sleep(3000);
}
finally
{
sw.Stop();
var tim = sw.Elapsed;
var elapsedMS = tim.Seconds * 1000 + tim.Milliseconds;
t--;
Console.WriteLine("h={0}, h2={1}, threads={2} [end, sleep time={3} ms] ", h, h2, t, elapsedMS);
}
}
}
What I've modified here is the following:
- Timer interval is now 10 ms, the threads still have 3000 ms. The effect is that while threads are sleeping, new threads will be created
- I've added varialbe
t
, which counts the number of threads currently being active (it is increased when the thread starts and decreased right before the thread ends)
- I've added 2 dump statements, printing out the thread start and the thread end
- Finally, I've given the parameter of function
doWork
a different name (h2), which allows to see the value of the underlying variable h
Now it is intersting to see the output of this modified program in LinqPad (note the values are not always the same as they're depending on the race conditions of the started threads):
h=1, h2=1, threads=1 [start]
h=2, h2=2, threads=2 [start]
h=3, h2=3, threads=3 [start]
h=4, h2=4, threads=4 [start]
h=5, h2=5, threads=5 [start]
...
h=190, h2=190, threads=190 [start]
h=191, h2=191, threads=191 [start]
h=192, h2=192, threads=192 [start]
h=193, h2=193, threads=193 [start]
h=194, h2=194, threads=194 [start]
h=194, h2=2, threads=192 [end]
h=194, h2=1, threads=192 [end]
h=194, h2=3, threads=191 [end]
h=195, h2=195, threads=192 [start]
I think the values speak for themselves: What is happening is that every 10 ms a new thread is started, while others are still sleeping. Also interesting is to see that h is not always equal to h2, especially not if more threads are started while others are sleeping. The number of threads (variable t) is after a while stabilizing, i.e. running around 190-194.
You might argue, that we need to put locks on the variables t and h, for example
readonly object o1 = new object();
int _t=0;
int t {
get {int tmp=0; lock(o1) { tmp=_t; } return tmp; }
set {lock(o1) { _t=value; }}
}
While that is a cleaner approach, it didn't change the effect shown in this example.
Now, in order to prove that each thread really sleeps 3000ms (= 3s), let's add a Stopwatch
to the worker thread doWork
:
public void doWork(int h2)
{
Stopwatch sw = new Stopwatch(); sw.Start();
try
{
t++; string.Format("h={0}, h2={1}, threads={2} [start]",
h, h2, t).Dump();
Thread.Sleep(3000); }
finally {
sw.Stop(); var tim = sw.Elapsed;
var elapsedMS = tim.Seconds*1000+tim.Milliseconds;
t--; string.Format("h={0}, h2={1}, threads={2} [end, sleep time={3} ms] ",
h, h2, t, elapsedMS).Dump();
}
}
For a proper cleanup of the threads, let's disable the timer after the ReadLine
as follows:
Console.ReadLine(); t.Enabled=false;
This allows you to see what happens if no more threads are starting, after you've pressed ENTER:
...
h=563, h2=559, threads=5 [end, sleep time=3105 ms]
h=563, h2=561, threads=4 [end, sleep time=3073 ms]
h=563, h2=558, threads=3 [end, sleep time=3117 ms]
h=563, h2=560, threads=2 [end, sleep time=3085 ms]
h=563, h2=562, threads=1 [end, sleep time=3054 ms]
h=563, h2=563, threads=0 [end, sleep time=3053 ms]
You can see they are all being terminated one after the other as expected and they slept about 3s (or 3000ms).