我有这段代码:
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秒。
答案 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秒的延迟开始新的线程。它发生在这样:
如您所见,每个线程都会休眠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);
}
}
}
我在此修改的内容如下:
t
,它计算当前处于活动状态的线程数(当线程开始时增加并且在线程结束之前减少)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都在睡三秒钟,但是他们的睡眠会重叠,因为你会以一秒的间隔创建线程。