2009-11-08 22 views
9

Tôi có ứng dụng Dịch vụ Windows sử dụng một số Threading.TimerTimerCallback để thực hiện một số quy trình trong các khoảng thời gian cụ thể. Tôi cần phải khóa mã xử lý này xuống chỉ còn 1 luồng tại một thời điểm.Sử dụng khóa với Luồng.Timer

Ví dụ: dịch vụ được bắt đầu và cuộc gọi lại đầu tiên được kích hoạt và chuỗi được bắt đầu và bắt đầu xử lý. Điều này hoạt động ok miễn là quá trình xử lý được hoàn thành trước lần gọi lại tiếp theo. Vì vậy, ví dụ như quá trình xử lý mất nhiều thời gian hơn bình thường và TimerCallback được kích hoạt một lần nữa trong khi một luồng khác đang xử lý, tôi cần phải đợi cho đến khi chuỗi khác được thực hiện.

Dưới đây là một mẫu mã của tôi:

static Timer timer; 
static object locker = new object(); 

public void Start() 
{ 
    var callback = new TimerCallback(DoSomething); 
    timer = new Timer(callback, null, 0, 10000); 
} 

public void DoSomething() 
{ 
     lock(locker) 
     { 
      // my processing code 
     } 
} 

Đây có phải là một cách an toàn để làm điều này? Điều gì sẽ xảy ra nếu hàng đợi được khá đáng kể? Có lựa chọn nào tốt hơn không?

Trả lời

26

Nếu bạn có thể bật các sự kiện với khoảng thời gian không đổi giữa chúng (trái ngược với mã hiện tại kích hoạt chúng trong khoảng thời gian không đổi) thì bạn có thể khởi động bộ hẹn giờ mà không có dấu chấm và mỗi lần xếp hàng một cuộc gọi lại mới, ví dụ

static Timer timer; 

public void Start() 
{ 
    var callback = new TimerCallback(DoSomething); 
    timer = new Timer(callback, null, 0, Timeout.Infinite); 
} 

public void DoSomething() 
{ 
     try 
     { 
      // my processing code 
     } 
     finally 
     { 
      timer.Change(10000, Timeout.Infinite); 
     } 
} 

Mã này yêu cầu bộ hẹn giờ mới được tạo để kích hoạt ngay lập tức, chỉ một lần. Trong mã xử lý nó làm việc và sau đó nói với bộ đếm thời gian để bắn một lần nữa trong 10 giây, một lần duy nhất. Vì bộ hẹn giờ hiện không kích hoạt định kỳ nhưng đang được khởi động lại bằng phương thức gọi lại của nó, khi đó cuộc gọi lại được đảm bảo là một luồng đơn không có hàng đợi.

Nếu bạn muốn giữ khoảng cách không đổi, thì sẽ phức tạp hơn một chút vì bạn phải quyết định phải làm gì nếu quá trình xử lý bắt đầu mất nhiều thời gian hơn khoảng thời gian hẹn giờ. Một lựa chọn là làm những gì bạn đang làm, nhưng về cơ bản sẽ kết thúc với rất nhiều chủ đề xếp hàng và sự kiện đói trong hồ bơi. Một tùy chọn khác là chỉ cần hủy bỏ cuộc gọi lại nếu đã có tiến trình gọi lại, ví dụ:

static Timer timer; 
static object locker = new object(); 

public void Start() 
{ 
    var callback = new TimerCallback(DoSomething); 
    timer = new Timer(callback, null, 0, 10000); 
} 

public void DoSomething() 
{ 
     if (Monitor.TryEnter(locker)) 
     { 
      try 
      { 
       // my processing code 
      } 
      finally 
      { 
       Monitor.Exit(locker); 
      } 
     } 
} 
+0

Ý tưởng hay khi kích hoạt bộ hẹn giờ không bao giờ nghĩ về nó theo cách đó. Kịch bản thứ hai, điều này có nghĩa là luồng chỉ đơn giản là thoát và không đợi? – James

+0

Vâng, trong kịch bản thứ hai, TryEnter sẽ trả về ngay lập tức nếu có một luồng khác hiện đang trong mã xử lý, vì vậy nó bỏ qua khoảng thời gian hẹn giờ đó một cách hiệu quả. –

+0

Điều này nên làm các trick, cảm ơn. – James

1

Điều tồi tệ nhất có thể xảy ra nếu mã xử lý mất hơn 10s để thực hiện là bạn sẽ lãng phí 1 chủ đề threadpool mỗi khi có một cuộc gọi lại mới được gọi là (họ sẽ chờ đợi cho vào báo cáo kết khóa). Và nếu bạn lấy tất cả các thread thread HttpWebRequest, ASP.NET, các invocations ủy nhiệm không đồng bộ ... sẽ bị ảnh hưởng.

Điều tôi sẽ làm là lập lịch cuộc gọi lại đầu tiên ngay lập tức. Sau đó, nếu bạn thực sự cần DoSomething của bạn() được gọi mỗi 10s:

public void DoSomething() 
{ 
     DateTime start = DateTime.UtcNow; 
     ... 
     TimeSpan elapsed = (DateTime.UtcNow - start); 
     int due_in = (int) (10000 - elapsed.TotalMilliseconds); 
     if (due_in < 0) 
      due_in = 0; 
     timer.Change (due_in, Timeout.Infinite); 
} 

Hoặc một cái gì đó dọc theo dòng đó.

Các vấn đề liên quan