C#线程不会睡觉?

时间:2012-07-03 09:12:11

标签: c# .net multithreading

我有这段代码:

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 ms的延迟

如何让每个doWork睡眠时间为3000毫秒?

正如你在这里看到的那样,开始时有3秒的延迟,然后每次迭代1秒。

enter image description here

6 个答案:

答案 0 :(得分: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()。简单,并且不涉及大量线程。

答案 1 :(得分: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转储
  8. 如您所见,每个线程都会休眠3秒钟,但每秒都会发生转储。

    如何使用线程?像这样的人:

    void Main()
    {
        new Thread(() => doWork()).Start();
        Console.ReadLine();
    }
    
    public void doWork()
    {
        int h = 0;
        do
        {
            Thread.Sleep(3000);
            h.Dump();
            h++;
        }while(true);
    }
    

答案 2 :(得分: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 ms,线程仍然有3000 ms。结果是当线程处于休眠状态时,将创建新线程
  • 我添加了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; }} 
      }

虽然这是一种更清洁的方法,但它并未改变此示例中显示的效果。

现在,为了证明每个线程确实睡眠时间为3000毫秒(= 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)。

答案 3 :(得分:6)

您看到此行为的原因很简单:您每秒安排一个新线程,结果在三秒后可见。前四秒你没有看到任何东西;然后,三秒钟前启动的线程转储;到那时另一个线程已经睡了两秒钟,而另一个线程已经睡了两秒钟 - 一秒钟。下一个第二个线程#2转储;然后线程#3,#4等等 - 你每秒都会得到一个打印输出。

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

答案 4 :(得分:2)

好像你每秒都在运行一个新线程不是一个好主意,使用backgroundworker,当事件backgroundworker完成时再次调用C函数,这样你就不需要一个计时器

答案 5 :(得分:2)

每个doWork都在睡三秒钟,但是他们的睡眠会重叠,因为你会以一秒的间隔创建线程。