2011-03-24 103 views
30

每隔N分鐘,我們要運行一系列任務。因此,我們已經創建了一個任務執行與Thread.Sleep的替代方案()

do { DoWork(); }while(!stopRequested) 

現在,我們希望有工作週期之間的停頓。每個人似乎都認爲Thread.Sleep()是魔鬼。我已經看到使用Monitor/Event的提及,但我們沒有其他人告訴我們去做工作。我們只是想每隔N分鐘就像發條一樣做東西。

那麼有沒有其他的選擇或我找到了Thread.Sleep的有效使用?

有人在另一篇文章中提到了WaitHandle.WaitOne()作爲替代方案,但你不能從非靜態方法顯然調用它?或者至少,我不能,因爲我得到一個編譯時錯誤..

需要 的對象引用非靜態字段,方法或 財產 「System.Threading.WaitHandle.WaitOne (System.TimeSpan)'

回答

22

當然,您必須致電WaitOne a WaitHandle。這是一個實例方法。否則,它會如何知道要等待什麼?

最好是有你可以反應而不是睡覺,這樣你就可以注意到無需等待幾分鐘就取消了。 WaitHandle的另一種替代方法是使用Monitor.Wait/Pulse。但是,如果你使用的是.NET 4,我會研究Task Parallel Library提供的功能......它的級別比其他選項略高一些,通常是一個經過深思熟慮的庫。

對於你可能想看看使用Timer(無論是System.Threading.TimerSystem.Timers.Timer),或者甚至可能Quartz.NET經常性的工作任務。

+0

你認爲答案仍然有效嗎? – Cer 2017-03-30 11:40:37

+0

@Cer:它應該仍然有效,當然。另一種方法是使用具有取消標記的任務。 – 2017-03-30 12:36:20

7

Thread.Sleep不是魔鬼 - 你可以用它來做這樣的場景。這對於短時間來說不是很可靠。

使用WaitHandle是一個不錯的選擇 - 但您需要一個等待句柄的特定實例。但是,它不會單獨做這件事。

這就是說,大多數情況下,像這樣的操作更適合使用定時器。是否有理由試圖在循環中處理這個問題,而不是僅僅使用Timer來啓動工作項目?

+1

呃,我們會去缺乏想象力,因爲我沒有使用定時器:) – 2011-03-24 20:00:45

+0

不,'Thread.Sleep'不是魔鬼。它更像Phil的光明之王(http://en.wikipedia.org/wiki/List_of_Dilbert_characters#Phil_the_Prince_of_Insufficient_Light)。但嚴重的是,'Thread.Sleep'的使用相對較少。對我來說,這是代碼中的紅旗。大多數情況下,用計時器替換「睡眠」會產生更清晰,更健壯的代碼。 – 2011-03-24 21:05:05

5

,我能想到的把我的頭頂部的三個選項:

但我相信很多人會提到 - Thread.Sleep()並不是那麼糟糕。

2

您可以使用您設置時,它的時候停止,然後一個ManualResetEvent的做就可以了WaitOne的。

2

我會用一個等待定時器;這表示的AutoResetEvent。你的線程應該等待這個WaitHandle對象。下面是本方法一個小控制檯應用程序:

class Program { 
    const int TimerPeriod = 5; 
    static System.Threading.Timer timer; 
    static AutoResetEvent ev; 
    static void Main(string[] args) 
    { 
     ThreadStart start = new ThreadStart(SomeThreadMethod); 
     Thread thr = new Thread(start); 
     thr.Name = "background"; 
     thr.IsBackground = true; 
     ev = new AutoResetEvent(false); 
     timer = new System.Threading.Timer(
      Timer_TimerCallback, ev, TimeSpan.FromSeconds(TimerPeriod), TimeSpan.Zero); 
     thr.Start(); 
     Console.WriteLine(string.Format("Timer started at {0}", DateTime.Now)); 
     Console.ReadLine(); 
    } 

    static void Timer_TimerCallback(object state) { 
     AutoResetEvent ev = state as AutoResetEvent; 
     Console.WriteLine(string.Format 
      ("Timer's callback method is executed at {0}, Thread: ", 
      new object[] { DateTime.Now, Thread.CurrentThread.Name})); 
     ev.Set(); 
    } 

    static void SomeThreadMethod() { 
     WaitHandle.WaitAll(new WaitHandle[] { ev }); 
     Console.WriteLine(string.Format("Thread is running at {0}", DateTime.Now)); 
    } 
} 
0

你可以使用一個System.Timers.Timer,並在其過去處理程序執行工作。

0

至於其他的應答者說,計時器可以爲你工作好。

如果你想自己的線程,我就不會在這裏使用了Thread.Sleep,如果僅僅是因爲如果你需要關閉應用程序,有沒有什麼好辦法來告訴它退出休眠。我以前使用過這樣的東西。

class IntervalWorker 
{ 
    Thread workerThread; 
    ManualResetEventSlim exitHandle = new ManualResetEventSlim(); 

    public IntervalWorker() 
    { 
     this.workerThread = new Thread(this.WorkerThread); 
     this.workerThread.Priority = ThreadPriority.Lowest; 
     this.workerThread.IsBackground = true; 
    } 

    public void Start() 
    { 
     this.workerThread.Start(); 
    } 

    public void Stop() 
    { 
     this.exitHandle.Set(); 
     this.workerThread.Join(); 
    } 

    private void WorkerThread() 
    { 
     int waitTimeMillis = 10000; // first work 10 seconds after startup. 

     while (!exitHandle.Wait(waitTimeMillis)) 
     { 
      DoWork(); 

      waitTimeMillis = 300000; // subsequent work at five minute intervals. 
     } 
    } 
} 
0

我用兩個定時器在WinForms應用程序和控制檯應用程序啓動定期由ScheduledTask的服務器上。用計時器的WinForms已經在案件中使用時,我希望它彈出的通知,它跑過去,它找到了什麼(我已經設置這些來mimize到系統托盤)。對於我只想在服務器出現問題時收到通知的情況,我已將它們作爲控制檯應用程序放在服務器上,並將它們作爲計劃任務運行。這似乎工作得很好。

我想我試圖在我現在用的定時器的應用程序使用睡眠和不喜歡的結果。首先,使用計時器,我可以非常輕鬆地調用最小化的應用程序,以設置或更改前面的設置。如果應用程序已經睡着了,那麼您很難在睡眠中重新獲得訪問權限。

1

如果所有的線程正在做的是一樣的東西:

while (!stop_working) 
{ 
    DoWork(); 
    Thread.Sleep(FiveMinutes); 
} 

那我就沒有使用一個線程在所有建議。首先,沒有什麼特別好的理由會導致花費大部分時間睡眠的專用線程的系統開銷。其次,如果在線程停止睡眠後30秒設置stop_working標誌,則必須等待四分半鐘才能喚醒並終止線程。

我建議,正如其他人:用一個定時器:

System.Threading.Timer WorkTimer; 

// Somewhere in your initialization: 

WorkTimer = new System.Threading.Timer((state) => 
    { 
     DoWork(); 
    }, null, TimeSpan.FromMinutes(5.0), TimeSpan.FromMinutes(5.0)); 

而關閉:

WorkTimer.Dispose(); 
47

按照我的理解,Thread.sleep()方法是不好的,因爲它迫使線程的資源超出緩存,所以必須在之後再次加載。沒什麼大不了的,但它可能會在高負載情況下加劇性能問題。然後還有一個事實,即計時不準確,它有效地不能等待的持續時間在10ms左右...

我用這個片段:

new System.Threading.ManualResetEvent(false).WaitOne(1000); 

容易餡餅,它全部適合一條線。創建一個永遠不會設置的新事件處理程序,然後等待完整的超時期限,您將其指定爲參數WaitOne()

雖然,對於這種特定情況下,計時器可能會是一個更合適的方法:

var workTimer = new System.Threading.Timer(
    (x) => DoWork(), 
    null, 
    1000, // initial wait period 
    300000); // subsequent wait period 

然後,而不是設置取消變量,你將與workTimer.Stop()停止計時器。


編輯

因爲人們仍在尋找這個有用,我要補充的是.NET 4.5引入了Task.Delay方法,這更加簡潔,同時還支持異步:

Task.Delay(2000).Wait(); // Wait 2 seconds with blocking 
await Task.Delay(2000); // Wait 2 seconds without blocking 
+2

我發現你的1行代碼對於尋找thread.sleep的替代品時非常有用,我只想暫停一秒或2秒,並且每次都不能使用thread.sleep登錄到控制檯。 – Sam 2013-03-18 21:26:07

+2

我注意到'ManualResetEvent'是'IDisposable',但是你的代碼讓匿名實例變成GC'd。在這種情況下是否存在沒有明確調用Dispose的風險? – Dai 2016-06-27 22:32:11

+1

好問題。根據我在反彙編中可以看到的,'ManualResetEvent'使用基本的'WaitHandle'功能,其Dispose方法僅僅處理其內部的'SafeWaitHandle'實例。 SafeWaitHandle的基類SafeHandle在其解構器中顯式調用它自己的Dispose方法,所以我假設任何非託管OS句柄在該對象被GC'd處理時都會被清除。但2016年最好的選擇就是使用'Task.Delay'並避免整個問題。 :) – 2016-11-21 20:01:27

-1

我發現了一種方法來破解Thread.Sleep,讓程序定期喚醒,並在需要取消時執行事件。如下所示:

// Pay attention every 1/10 sec to maintain some UI responsiveness 
while (val > 0) { 
    Thread.Sleep(100); 
    val = val - 100; 
    Application.DoEvents(); 
    if (val == x) { 
     // You could do some conditional stuff here at specific times 
    } 
} 

替換val用你想要的毫秒延遲。越是降低ms值的Thread.Sleep,程序看起來就越有反應。 100ms似乎可以接受我。 50ms是最好的。