2009-11-09 39 views
16

Tôi có một System.Threading.Timer gọi trình xử lý sự kiện thích hợp của nó (gọi lại) mỗi 10 ms. Bản thân phương thức là không reentrant và đôi khi có thể mất chiều dài hơn 10 mili giây. Vì vậy, tôi muốn dừng bộ đếm thời gian trong khi thực thi phương thức.Dừng hẹn giờ trong phương thức gọi lại

Code:

private Timer _creatorTimer; 

// BackgroundWorker's work 
private void CreatorWork(object sender, DoWorkEventArgs e) { 
     _creatorTimer = new Timer(CreatorLoop, null, 0, 10); 

     // some other code that worker is doing while the timer is active 
     // ... 
     // ... 
} 

private void CreatorLoop(object state) { 
     // Stop timer (prevent reentering) 
     _creatorTimer.Change(Timeout.Infinite, 0); 

     /* 
      ... Work here 
     */ 

     // Reenable timer 
     _creatorTimer.Change(10, 0); 
} 

MSDN khẳng định rằng phương pháp gọi lại được gọi là (mỗi lần cháy timer) trong chủ đề riêng biệt từ các hồ bơi thread. Điều đó có nghĩa là nếu tôi ngừng bộ hẹn giờ thì điều đầu tiên trong phương thức nó vẫn không cần thiết ngăn bộ hẹn giờ kích hoạt và chạy một thể hiện khác của phương thức trước khi người đầu tiên có cơ hội dừng hẹn giờ.

Có lẽ bộ hẹn giờ (hoặc thậm chí chính phương pháp không reentrant) bị khóa? Cách đúng để ngăn bộ hẹn giờ kích hoạt trong khi thực hiện phương thức gọi lại (và không reentrant) của nó là gì?

+0

câu hỏi này có thể giúp bạn ra ngoài http://stackoverflow.com/questions/1116249/manualresetevent-vs-thread-sleep – Kane

Trả lời

45

Bạn có thể cho phép bộ hẹn giờ tiếp tục kích hoạt phương thức gọi lại nhưng bọc mã không lặp lại của bạn trong Monitor.TryEnter/Exit. Không cần phải dừng/khởi động lại bộ hẹn giờ trong trường hợp đó; các cuộc gọi trùng lặp sẽ không lấy được khóa và quay lại ngay lập tức.

private void CreatorLoop(object state) 
{ 
    if (Monitor.TryEnter(lockObject)) 
    { 
    try 
    { 
     // Work here 
    } 
    finally 
    { 
     Monitor.Exit(lockObject); 
    } 
    } 
} 
+0

+1 Tôi chưa bao giờ nghĩ đến việc sử dụng TryEnter; điều đó rất thú vị. – Schmuli

+0

Điều này dường như đang thực hiện thủ thuật. Hai hoặc nhiều chủ đề có thể nhập phương thức, nhưng chỉ một chủ đề mới thực sự hoạt động. Tôi cũng đã ngừng bộ đếm thời gian sau khi Monitor.TryEnter() để nó không cháy ở tất cả trong quá trình thực hiện (không cần nó để bắn), chỉ trong trường hợp thời gian thực hiện lớn hơn nhiều so với thời gian của bộ định thời. Bộ hẹn giờ được khởi động lại sau khi hoàn thành công việc trong phương thức. –

+0

+1 Giải pháp tuyệt vời! – ParmesanCodice

0

Tôi đã có tình huống tương tự với một System.Timers.Timer, trong đó sự kiện đã trôi qua được thực thi từ một luồng và cần phải reentrant.

tôi đã sử dụng phương pháp này để có được xung quanh vấn đề này:

private void tmr_Elapsed(object sender, EventArgs e) 
{ 
    tmr.Enabled = false; 
    // Do Stuff 
    tmr.Enabled = true; 
} 

Tùy thuộc vào những gì bạn đang làm, bạn có thể muốn xem xét một System.Timers.Timer, đây là một bản tóm tắt đẹp từ MSDN

          System.Windows.Forms System.Timers   System.Threading 
Timer event runs on what thread?   UI thread    UI or worker thread Worker thread 
Instances are thread safe?    No      Yes     No 
Familiar/intuitive object model?   Yes      Yes     No 
Requires Windows Forms?     Yes      No     No 
Metronome-quality beat?     No      Yes*     Yes* 
Timer event supports state object?  No      No     Yes 
Initial timer event can be scheduled? No      No     Yes 
Class supports inheritance?    Yes      Yes     No 

* Depending on the availability of system resources (for example, worker threads)    
+0

tôi tin rằng điều này không thực sự gặp vấn đề. Vâng, nó có thể trong 99,9% trường hợp, nhưng nếu hệ thống không cung cấp thời gian xử lý cho trình xử lý sự kiện của bạn trước lần bắn sự kiện Elapsed tiếp theo, hai luồng khác nhau có thể thực thi phương thức song song. –

+0

Điểm tốt! Bạn luôn có thể sử dụng điều này kết hợp với một giải pháp khóa như của jsw – ParmesanCodice

6

Một vài giải pháp khả thi:

  • có công việc thực tế thực hiện trong thêm một đại biểu chủ đề đó là chờ đợi vào một sự kiện. Gọi lại bộ hẹn giờ chỉ báo hiệu sự kiện. Chuỗi công nhân không thể được nhập lại, vì đó là một chuỗi đơn lẻ chỉ hoạt động khi sự kiện được báo hiệu. Bộ hẹn giờ là reentrant, vì tất cả nó là tín hiệu sự kiện (có vẻ như một vòng xoay nhỏ và lãng phí, nhưng nó sẽ hoạt động)
  • có bộ hẹn giờ được tạo ra chỉ với thời gian chờ bắt đầu và không có thời gian chờ định kỳ. . Gọi lại hẹn giờ sẽ hủy bỏ đối tượng hẹn giờ đó và tạo một đối tượng mới khi nó đã hoàn thành công việc của nó cũng sẽ chỉ kích hoạt một lần.

Bạn có thể để quản lý tùy chọn # 2 mà không xử lý/tạo một đối tượng mới bằng cách sử dụng phương pháp Change() của đối tượng timer gốc, nhưng tôi không chắc chắn những gì hành vi này là chính xác gọi Change() với một mới thời gian chờ bắt đầu sau khi hết thời gian chờ đầu tiên. Đó sẽ là giá trị một hoặc hai thử nghiệm.

Edit:


tôi đã làm bài kiểm tra - thao tác đếm thời gian như một restartable one-shot dường như làm việc một cách hoàn hảo, và nó đơn giản hơn nhiều so với các phương pháp khác.Dưới đây là một số mẫu mã dựa trên của bạn như là một điểm khởi đầu (một vài chi tiết có thể thay đổi để làm cho nó biên dịch trên máy tính của tôi):

private Timer _creatorTimer; 

// BackgroundWorker's work 
private void CreatorWork(object sender, EventArgs e) { 
    // note: there's only a start timeout, and no repeat timeout 
    // so this will fire only once 
    _creatorTimer = new Timer(CreatorLoop, null, 1000, Timeout.Infinite); 

    // some other code that worker is doing while the timer is active 
    // ... 
    // ... 
} 

private void CreatorLoop(object state) { 
    Console.WriteLine("In CreatorLoop..."); 
    /* 
     ... Work here 
    */ 
    Thread.Sleep(3000); 

    // Reenable timer 
    Console.WriteLine("Exiting..."); 

    // now we reset the timer's start time, so it'll fire again 
    // there's no chance of reentrancy, except for actually 
    // exiting the method (and there's no danger even if that 
    // happens because it's safe at this point). 
    _creatorTimer.Change(1000, Timeout.Infinite); 
} 
+0

Điều này dường như hoạt động, có, nhưng mã trở nên ít sạch hơn khi có nhiều kết quả của phương pháp (nhiều ngành và trường hợp ngoại lệ khác). Nhưng có vẻ là một giải pháp tốt khi nói đến hiệu suất vì không có cơ chế đồng bộ được sử dụng. Khác hơn thế, điều này sẽ KHÔNG hoạt động nếu có các phương thức/luồng/bộ đếm thời gian khác có thể thử nhập phương thức này. Sau đó, tất nhiên, chúng tôi đang reentering phương pháp không reentrant, đó là nơi mà màn hình làm việc tốt hơn. Cảm ơn cho giải pháp và thử nghiệm, anyway. Đó là một ý tưởng hay. –

+0

Sự phức tạp của mã không còn là vấn đề nữa nếu bạn đang sử dụng một mutex - chỉ cần bọc mã trong 'try' /' finally' hoặc đơn giản gọi một thường trình khác có độ phức tạp và để thói quen gọi lại bộ định thời đơn giản 'gọi rồi đặt lại hẹn giờ'. Nếu gọi lại sẽ được sử dụng bởi nhiều bộ đếm thời gian, sau đó có kỹ thuật này sẽ không hoạt động và bạn sẽ cần một đối tượng đồng bộ hóa thực sự. Tôi thấy rằng nó là khá phổ biến cho một callback hẹn giờ được sử dụng bởi nhiều tính giờ - đặc biệt là khi gọi lại là rất phức tạp (thường có nghĩa là gọi lại bộ đếm thời gian được thiết kế cho một mục đích rất cụ thể). –

+0

Bạn có thể đạt được hành vi tương tự này bằng cách sử dụng System.Timers.Timer mà tôi nghĩ là dễ dàng hơn nhiều. Xem http://stackoverflow.com/questions/7055820/non-reentrant-timers/ –

0

tôi làm điều đó với đan cài cung cấp các hoạt động nguyên tử, và bởi CompareExchange đảm bảo rằng chỉ có một thread tại một thời điểm bước vào phần quan trọng:

private int syncPoint = 0; 

private void Loop() 
    { 
     int sync = Interlocked.CompareExchange(ref syncPoint, 1, 0); 
     //ensures that only one timer set the syncPoint to 1 from 0 
     if (sync == 0) 
     { 
      try 
      { 
       ... 
      } 
      catch (Exception pE) 
      { 
       ... 
      } 
      syncPoint = 0; 
     } 

    } 
0
//using Timer with callback on System.Threading namespace 
    // Timer(TimerCallback callback, object state, int dueTime, int period); 
    //  TimerCallback: delegate to callback on timer lapse 
    //  state: an object containig information for the callback 
    //  dueTime: time delay before callback is invoked; in milliseconds; 0 immediate 
    //  period: interval between invocation of callback; System.Threading.Timeout.Infinity to disable 
    // EXCEPTIONS: 
    //  ArgumentOutOfRangeException: negative duration or period 
    //  ArgumentNullException: callback parameter is null 

    public class Program 
    { 
     public void Main() 
     { 
      var te = new TimerExample(1000, 2000, 2); 
     } 
    } 

    public class TimerExample 
    { 
     public TimerExample(int delayTime, int intervalTime, int treshold) 
     { 
      this.DelayTime = delayTime; 
      this.IntervalTime = intervalTime; 
      this.Treshold = treshold; 
      this.Timer = new Timer(this.TimerCallbackWorker, new StateInfo(), delayTime, intervalTime); 
     } 

     public int DelayTime 
     { 
      get; 
      set; 
     } 

     public int IntervalTime 
     { 
      get; 
      set; 
     } 

     public Timer Timer 
     { 
      get; 
      set; 
     } 

     public StateInfo SI 
     { 
      get; 
      set; 
     } 

     public int Treshold 
     { 
      get; 
      private set; 
     } 

     public void TimerCallbackWorker(object state) 
     { 
      var si = state as StateInfo; 

      if (si == null) 
      { 
       throw new ArgumentNullException("state"); 
      } 

      si.ExecutionCounter++; 

      if (si.ExecutionCounter > this.Treshold) 
      { 
       this.Timer.Change(Timeout.Infinite, Timeout.Infinite); 
       Console.WriteLine("-Timer stop, execution reached treshold {0}", this.Treshold); 
      } 
      else 
      { 
       Console.WriteLine("{0} lapse, Time {1}", si.ExecutionCounter, si.ToString()); 
      } 
     } 

     public class StateInfo 
     { 
      public int ExecutionCounter 
      { 
       get; 
       set; 
      } 

      public DateTime LastRun 
      { 
       get 
       { 
        return DateTime.Now; 
       } 
      } 

      public override string ToString() 
      { 
       return this.LastRun.ToString(); 
      } 
     } 
    } 

    // Result: 
    // 
    // 1 lapse, Time 2015-02-13 01:28:39 AM 
    // 2 lapse, Time 2015-02-13 01:28:41 AM 
    // -Timer stop, execution reached treshold 2 
    // 
+0

Tôi khuyên bạn nên sử dụng trình định dạng mã hoặc thứ gì đó để dọn dẹp mã đó, lại. khoảng cách, định dạng, vv Nó có thể là tuyệt vời, nhưng lần hiển thị đầu tiên đếm, và ấn tượng đầu tiên của tôi nếu tôi không muốn sử dụng mã trông như thế. – ProfK

+0

Đó là một ví dụ nhanh nhưng làm việc, hy vọng nó có thể đọc được ngay bây giờ. – BTE

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