2012-07-03 82 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)。