2012-07-03 54 views
54

我有这样的代码:C#线程不会睡觉?

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秒。

enter image description here

+5

我没看到问题。发生的事情正是您对多线程场景的期望。 – leppie

+0

注意:***这是一个陷阱! ***你所看到的与你想达到的目标无关,我相信。 –

+0

我也没有在这里看到任何问题 - 当你在每次产生三秒睡眠的线程产卵时,但产卵每秒发生一次,那么你有一个最初的延迟,因为第一个必须“等待”三秒钟通过,但所有其他人正在跟随一秒钟的偏移量。 – Gorgsenegger

回答

65

每次计时器滴答时,您都会启动一个线程进行一些睡眠;该线程完全隔离,并且计时器将每秒继续触发。实际上,定时器触发每秒即使您移动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()在这里的用途。另一个选项是while循环,其中包含Sleep()。简单,并且不涉及大量的线程。

+2

感谢您真正解释发生了什么! – aukaost

+0

@MarcGravell谢谢(我知道,使用线程是昂贵的...还只是学习。) –

+6

@RoyiNamir如果这是学习,那么请了解,这是在做计划工作的一个非常昂贵的方式,P –

2

好像你正在乳宁一个新的线程每一秒至极是不是一个好主意,使用BackgroundWorker的,而当事件BackgroundWorker的完成再次调用C函数,这样你不会需要一个计时器

+2

我不想使用backgroundworker。我想使用纯线程命令... :) –

+0

然后只是删除计时器代码,并做一个递归样式/模式,当年线程完成睡眠1秒,再次调用C函数/方法 – JohnnBlade

+2

“我不想要要使用正确的工具,我想使用错误的工具“对解决方案没有多大帮助。 –

2

每个的doWork是睡三秒钟,但他们睡觉重叠,因为你创建每隔一秒的线程。

6

你看到此行为很简单的道理:你安排一个新的线程每一秒,结果变得可见三秒钟之后。前四秒你什么都看不到。然后,三秒钟之前启动的线程转储;另一个线程到那时已经睡了两秒,又一个线程 - 一秒钟。下一个第二个线程#2转储;然后是线程#3,#4等等 - 每秒都会得到一个打印输出。

如果您希望每三秒钟看到一个打印输出,您应该每三秒安排一个新线程,并带有任意延迟:初始线程将在三秒内加上延迟输出;所有后续的线程将以三秒的间隔进行触发。

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秒,但转储发生每一秒。

如何与线程一起工作?像这样:

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

public void doWork() 
{ 
    int h = 0; 
    do 
    { 
     Thread.Sleep(3000); 
     h.Dump(); 
     h++; 
    }while(true); 
} 
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(= 3秒),让我们添加一个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] 

你可以看到他们都被终止一前一后的预期和他们睡觉3s左右(或3000ms)。