あなたの例は非常に興味深いものです-それは並列処理の副作用を示しています。あなたの質問に答えるために、そして副作用を見やすくするために、私はあなたの例を少し修正しました:
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);
}
}
}
ここで変更したのは次のとおりです。
- タイマー間隔は10ミリ秒になりましたが、スレッドにはまだ3000ミリ秒あります。その効果は、スレッドがスリープしている間に、新しいスレッドが作成されることです。
- 現在アクティブなスレッドの数をカウントするvarialbeを追加しまし
t
た(スレッドが開始すると増加し、スレッドが終了する直前に減少します)
- スレッドの開始と終了を出力する2つのダンプステートメントを追加しました
- 最後に、関数のパラメーターに
doWork
別の名前(h2)を付けました。これにより、基になる変数hの値を確認できます。
これで、 LinqPadでこの変更されたプログラムの出力を確認することができます(開始されたスレッドの競合状態に応じて、値が常に同じであるとは限らないことに注意してください)。
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]
値はそれ自体を物語っていると思います。何が起こっているのかというと、他のスレッドがまだ眠っている間に、10ミリ秒ごとに新しいスレッドが開始されます。また興味深いのは、hが常にh2と等しいとは限らないことです。特に、他のスレッドがスリープしている間にさらに多くのスレッドが開始された場合はそうではありません。スレッドの数(変数t)はしばらくすると安定します。つまり、190〜194前後で実行されます。
たとえば、変数tとhをロックする必要があると主張するかもしれません。
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; }}
}
これはよりクリーンなアプローチですが、この例に示されている効果は変わりませんでした。
ここで、各スレッドが実際に3000ms(= 3s)スリープしていることを証明するためStopwatch
に、ワーカースレッドにを追加しましょう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();
}
}
ReadLine
スレッドを適切にクリーンアップするために、次のようにタイマーを無効にしましょう。
Console.ReadLine(); t.Enabled=false;
これにより、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]
予想どおり、すべてが次々に終了し、約3秒(または3000ミリ秒)スリープしたことがわかります。