2008-12-24 19 views
50

Tôi có phương thức sẽ bị trì hoãn trong một khoảng thời gian nhất định.So sánh bằng cách sử dụng Thread.Sleep và Timer để thực hiện chậm

Tôi có nên sử dụng

Thread thread = new Thread(() => { 
    Thread.Sleep(millisecond); 
    action(); 
}); 
thread.IsBackground = true; 
thread.Start(); 

Hoặc

Timer timer = new Timer(o => action(), null, millisecond, -1); 

Tôi đã đọc một số articles về việc sử dụng Thread.Sleep là thiết kế xấu. Nhưng tôi không hiểu tại sao.

Nhưng để sử dụng Bộ hẹn giờ, Bộ hẹn giờ có phương pháp xử lý. Kể từ khi thực hiện bị trì hoãn, tôi không biết làm thế nào để vứt bỏ Timer. Bạn có đề nghị nào không?

Hoặc nếu bạn có các mã thay thế để thực hiện chậm cũng được đánh giá cao.

Trả lời

39

Một sự khác biệt là System.Threading.Timer gửi cuộc gọi lại trên chuỗi chủ đề chuỗi, thay vì tạo chuỗi mới mỗi lần. Nếu bạn cần điều này xảy ra nhiều lần trong suốt quá trình ứng dụng, điều này sẽ tiết kiệm chi phí cho việc tạo và hủy một loạt các luồng (một quá trình rất tốn tài nguyên, như bài báo bạn chỉ ra), vì nó sẽ chỉ cần sử dụng lại các chủ đề trong hồ bơi, và nếu bạn sẽ có nhiều hơn một bộ đếm thời gian đi cùng một lúc nó có nghĩa là bạn sẽ có ít đề tài chạy cùng một lúc (cũng tiết kiệm tài nguyên đáng kể).

Nói cách khác, Bộ hẹn giờ sẽ hiệu quả hơn nhiều. Nó cũng có thể chính xác hơn, vì Thread.Sleep chỉ được đảm bảo chờ đợi ở mức LEAST miễn là số lượng thời gian bạn chỉ định (hệ điều hành có thể đặt nó vào giấc ngủ lâu hơn nữa). Cấp, Timer vẫn sẽ không chính xác chính xác, nhưng mục đích là kích hoạt callback càng gần thời gian được chỉ định càng tốt, trong khi điều này KHÔNG nhất thiết là ý định của Thread.Sleep.

Để hủy bộ hẹn giờ, cuộc gọi lại có thể chấp nhận tham số, vì vậy bạn có thể tự chuyển thông số hẹn giờ làm tham số và gọi Vứt bỏ trong cuộc gọi lại (mặc dù tôi chưa thử - tôi đoán là có thể là Bộ hẹn giờ có thể bị khóa trong khi gọi lại).

Chỉnh sửa: Không, tôi đoán bạn không thể thực hiện việc này, vì bạn phải chỉ định tham số gọi lại trong chính hàm tạo bộ đếm giờ.

Có thể giống như thế này? (Một lần nữa, đã không thực sự cố gắng nó)

class TimerState 
{ 
    public Timer Timer; 
} 

... và bắt đầu hẹn giờ:

TimerState state = new TimerState(); 

lock (state) 
{ 
    state.Timer = new Timer((callbackState) => { 
     action(); 
     lock (callbackState) { callbackState.Timer.Dispose(); } 
     }, state, millisecond, -1); 
} 

Các khóa nên ngăn chặn sự hẹn giờ gọi lại từ cố gắng để giải phóng bộ đếm thời gian trước khi Timer trường đã được đặt.


Phụ Lục: Khi commenter chỉ ra, nếu hành động() làm điều gì đó với giao diện người dùng, sau đó sử dụng một System.Windows.Forms.Timer có lẽ là một cược tốt hơn, vì nó sẽ chạy callback trên giao diện người dùng chủ đề. Tuy nhiên, nếu đây không phải là trường hợp, và nó xuống Thread.Sleep vs. Threading.Timer, Threading.Timer là con đường để đi.

+4

Nó cũng đáng để chỉ ra sự khác biệt với System.Windows.Forms.Timer, mà tôi * tin * gọi một hàm trên chuỗi giao diện người dùng, điều này thực sự quan trọng đối với các ứng dụng WinForms! –

+2

Đối với người đọc trong tương lai, 'Sleep' không phải là sự kiện được đảm bảo là ÍT NHẤT, nó được ghi nhận rằng nó có thể ít hơn. –

+4

Ngoài sự tò mò ... tài liệu đó ở đâu? –

13

Tôi nghĩ Thread.Sleep là tốt nếu bạn thực sự muốn tạm dừng ứng dụng trong một khoảng thời gian nhất định. Tôi nghĩ lý do mọi người nói đó là một thiết kế tồi tệ bởi vì trong hầu hết các trường hợp, mọi người không thực sự muốn ứng dụng tạm dừng.

Ví dụ, tôi đang làm việc trên một máy khách pop3 nơi lập trình viên đang sử dụng Thread.Sleep (1000) để chờ trong khi ổ cắm lấy thư. Trong tình huống đó, tốt hơn là kết nối một trình xử lý sự kiện vào socket và tiếp tục thực hiện chương trình sau khi socket đã hoàn thành.

+2

Trừ trường hợp không có ví dụ trong bài đăng là ứng dụng thực sự tạm dừng. Các poster là làm tạm dừng trên một thread riêng biệt, không gọi Thread.Sleep trực tiếp. Gọi Thread.Sleep trực tiếp, có, ý tưởng tồi. –

1

Thịt bò duy nhất mà tôi có với System.Timer là hầu hết thời gian tôi đã thấy nó được sử dụng cho sự chậm trễ (giờ, phút) trong dịch vụ bỏ phiếu và nhà phát triển thường quên khởi chạy sự kiện Trước khi bộ hẹn giờ. Điều này có nghĩa là nếu tôi khởi động ứng dụng hoặc dịch vụ, tôi phải chờ cho đến khi bộ đếm thời gian trôi qua (giờ, phút) trước khi nó thực sự thực thi.

Chắc chắn, đây không phải là vấn đề với bộ hẹn giờ, nhưng tôi nghĩ rằng nó thường được sử dụng không đúng bởi vì nó quá dễ sử dụng.

16

sử dụng ThreadPool.RegisterWaitForSingleObject thay vì hẹn giờ:

//Wait 5 seconds then print out to console. 
//You can replace AutoResetEvent with a Semaphore or EventWaitHandle if you want to execute the command on those events and/or the timeout 
System.Threading.ThreadPool.RegisterWaitForSingleObject(new AutoResetEvent(false), (state, bTimeout) => Console.WriteLine(state), "This is my state variable", TimeSpan.FromSeconds(5), true); 
+0

Đọc câu trả lời được chấp nhận. Bộ hẹn giờ đã sử dụng nhóm chủ đề. –

+13

Tôi thích RegisterWaitForSingleObject cho logic ... Phương pháp được thực thi ONCE khi hết thời gian ... vì vậy bạn không cần phải lừa để dừng hẹn giờ khi công việc được thực hiện không tốt ... để RegisterWaitForSingleObject tự nhiên làm chính xác những gì anh ta muốn, hẹn giờ không hẹn giờ là tốt hơn khi bạn muốn thực hiện một nhiệm vụ nhiều lần trong khoảng thời gian cụ thể .... –

+0

Tôi đã thêm một ví dụ. Chi tiết khác có sẵn tại câu hỏi này http://stackoverflow.com/questions/1535262/net-timeouts-waitforsingleobject-vs-timer –

0

@miniscalope Không không sử dụng ThreadPool.RegisterWaitForSingleObject thay vì hẹn giờ, System.Threading.Timer sẽ xếp hàng một callback được thực hiện trên một sợi bơi thread khi thời gian trôi qua và không yêu cầu xử lý chờ đợi, chờ đối tượng đơn lẻ sẽ kết thúc chuỗi threadpool chờ sự kiện được báo hiệu hoặc hết thời gian chờ trước khi chuỗi gọi lại.

+0

Bạn có một số tài liệu về điều này không? Về cơ bản bạn đang nói 'ThreadPool.RegisterWaitForSingleObject' hoạt động giống như' Thread.Sleep'by nói: * "chờ đối tượng đơn lẻ sẽ buộc một thread threadpool chờ sự kiện được báo hiệu hoặc hết thời gian chờ trước khi thread gọi lại" *? – atconway

+0

Nó không sử dụng Thread.Sleep ... sau khi đào qua nguồn nó ngụ ý rằng nó gọi là RegisterWaitForSingleObject (tôi nói ngụ ý bởi vì phương thức nó gọi là extern nhưng được trang trí như một cuộc gọi thời gian chạy nội bộ ...) là chính xác, bạn có thể giả định (từ [tài liệu] (http://msdn.microsoft.com/en-us/library/windows/desktop/ms685061 (v = vs.85) .aspx)) rằng nó sẽ sử dụng chờ chủ đề từ threadpool gốc để chờ cho đối tượng được báo hiệu, sau đó thực hiện cuộc gọi lại trên một chuỗi công nhân. – Matt

2

Tôi nhớ triển khai giải pháp tương tự giải pháp của Eric. Tuy nhiên, đây là một cách làm việc;)

class OneTimer 
    { 
     // Created by Roy Feintuch 2009 
     // Basically we wrap a timer object in order to send itself as a context in order to dispose it after the cb invocation finished. This solves the problem of timer being GCed because going out of context 
     public static void DoOneTime(ThreadStart cb, TimeSpan dueTime) 
     { 
      var td = new TimerDisposer(); 
      var timer = new Timer(myTdToKill => 
      { 
       try 
       { 
        cb(); 
       } 
       catch (Exception ex) 
       { 
        Trace.WriteLine(string.Format("[DoOneTime] Error occured while invoking delegate. {0}", ex), "[OneTimer]"); 
       } 
       finally 
       { 
        ((TimerDisposer)myTdToKill).InternalTimer.Dispose(); 
       } 
      }, 
         td, dueTime, TimeSpan.FromMilliseconds(-1)); 

      td.InternalTimer = timer; 
     } 
    } 

    class TimerDisposer 
    { 
     public Timer InternalTimer { get; set; } 
    } 
Các vấn đề liên quan