54

私はこのコードを持っています:

void Main()
{
    System.Timers.Timer t = new System.Timers.Timer (1000);
    t.Enabled=true;
    t.Elapsed+= (sender, args) =>c();
    Console.ReadLine();

}

int h=0;
public void c()
{
    h++;
    new Thread(() => doWork(h)).Start();
}

public void doWork(int h)
{
    Thread.Sleep(3000);
    h.Dump();
}

間隔が1000ミリ秒で、ジョブプロセスが3000ミリ秒の場合にどうなるかを確認したかったのです。

しかし、私は奇妙な振る舞いを見ました-3000ミリ秒の遅延は開始時にのみ発生します!

どうすれば各doWorkスリープを3000ミリ秒にすることができますか?

ここでわかるように、最初は3秒の遅延があり、その後、それぞれ1秒ずつ繰り返されます。

ここに画像の説明を入力してください

4

6 に答える 6

65

タイマーが作動するたびに、スレッドを開始してスリープを実行します。そのスレッドは完全に分離されており、タイマーは毎秒起動し続けます。実際には、に移動してタイマーは1秒ごとに起動Sleep(3000)しますc()

あなたが現在持っているものは:

1000 tick (start thread A)
2000 tick (start thread B)
3000 tick (start thread C)
4000 tick (start thread D, A prints line)
5000 tick (start thread E, B prints line)
6000 tick (start thread F, C prints line)
7000 tick (start thread G, D prints line)
8000 tick (start thread H, E prints line)
...

あなたが何をしようとしているのかははっきりしていません。タイマーを起動したくないときに無効にして、準備ができたら再開することもできますが、Sleep()ここでの目的は不明です。もう1つのオプションは、を含むwhileループSleep()です。シンプルで、多くのスレッドを必要としません。

于 2012-07-03T09:18:51.483 に答える
15

毎秒、3秒の遅延で新しいスレッドを開始しています。これは次のように発生します。

  1. スレッド1開始
  2. スレッド2が開始し、スレッド1がスリープします
  3. スレッド3が開始し、スレッド2がスリープし、スレッド1がスリープします
  4. スレッド4の開始、スレッド3のスリープ、スレッド2のスリープ、スレッド1のスリープ
  5. スレッド5の開始、スレッド4のスリープ、スレッド3のスリープ、スレッド2のスリープ、スレッド1のダンプ
  6. スレッド6の開始、スレッド5のスリープ、スレッド4のスリープ、スレッド3のスリープ、スレッド2のダンプ
  7. スレッド7の開始、スレッド6のスリープ、スレッド5のスリープ、スレッド4のスリープ、スレッド3のダンプ

ご覧のとおり、各スレッドは3秒間スリープしますが、ダンプは1秒ごとに発生します。

スレッドをどのように処理しますか?このようなsmth:

void Main()
{
    new Thread(() => doWork()).Start();
    Console.ReadLine();
}

public void doWork()
{
    int h = 0;
    do
    {
        Thread.Sleep(3000);
        h.Dump();
        h++;
    }while(true);
}
于 2012-07-03T09:21:21.980 に答える
9

あなたの例は非常に興味深いものです-それは並列処理の副作用を示しています。あなたの質問に答えるために、そして副作用を見やすくするために、私はあなたの例を少し修正しました:

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ミリ秒)スリープしたことがわかります。

于 2012-07-03T11:46:32.937 に答える
6

この動作が表示される理由は単純です。毎秒新しいスレッドをスケジュールし、その結果が3秒後に表示されるようにします。最初の4秒間は何も表示されません。次に、3秒前に開始されたスレッドがダンプします。それまでに別のスレッドが2秒間スリープし、さらに別のスレッドが1秒間スリープしていました。次の2番目のスレッド#2はダンプします。次に、スレッド#3、#4など-毎秒プリントアウトを取得します。

3秒ごとに印刷出力を表示する場合は、3秒ごとに新しいスレッドをスケジュールし、必要な遅延を設定する必要があります。最初のスレッドは3秒と遅延で出力されます。後続のすべてのスレッドは3秒間隔で起動します。

于 2012-07-03T09:20:40.387 に答える
2

各doWorkは3秒間スリープしますが、1秒間隔でスレッドを作成するため、スリープは重複します。

于 2012-07-03T09:19:10.980 に答える
2

毎秒新しいスレッドを実行しているようですが、これはお勧めできません。backgroundworkerを使用してください。イベントのbackgroundworkerが完了したら、C関数を再度呼び出してください。そうすれば、タイマーは不要になります。

于 2012-07-03T09:15:11.380 に答える