System.Threading.Timer trong C # có vẻ như không hoạt động. Nó chạy rất nhanh sau mỗi 3 giây


112

Tôi có một đối tượng hẹn giờ. Tôi muốn nó được chạy mỗi phút. Cụ thể, nó sẽ chạy một OnCallBackphương thức và không hoạt động trong khi một OnCallBackphương thức đang chạy. Khi một OnCallBackphương thức kết thúc, nó (a OnCallBack) khởi động lại bộ đếm thời gian.

Đây là những gì tôi có ngay bây giờ:

private static Timer timer;

private static void Main()
{
    timer = new Timer(_ => OnCallBack(), null, 0, 1000 * 10); //every 10 seconds
    Console.ReadLine();
}

private static void OnCallBack()
{
    timer.Change(Timeout.Infinite, Timeout.Infinite); //stops the timer
    Thread.Sleep(3000); //doing some long operation
    timer.Change(0, 1000 * 10);  //restarts the timer
}

Tuy nhiên, nó dường như không hoạt động. Nó chạy rất nhanh sau mỗi 3 giây. Ngay cả khi tăng một khoảng thời gian (1000 * 10). Có vẻ như nó nhắm mắt làm ngơ1000 * 10

Tôi đã làm gì sai?


12
Từ Timer.Change: "Nếu thời gian đến hạn bằng không (0), phương thức gọi lại được gọi ngay lập tức.". Có vẻ như nó bằng không đối với tôi.
Damien_The_Un Believer 9/10/12

2
Có, nhưng vậy thì sao? cũng có một thời kỳ.
Alan Coromano

10
Vậy nếu cũng có kinh thì sao? Câu trích dẫn không đưa ra tuyên bố nào về giá trị khoảng thời gian. Nó chỉ nói "nếu giá trị này bằng 0, tôi sẽ gọi lại ngay lập tức".
Damien_The_Un Believer 9/10/12

3
Điều thú vị là nếu bạn đặt cả thời gian đến hạn và khoảng thời gian thành 0, bộ đếm thời gian sẽ chạy mỗi giây và bắt đầu ngay lập tức.
Kelvin

Câu trả lời:


230

Đây không phải là cách sử dụng chính xác của System.Threading.Timer. Khi bạn khởi tạo Bộ hẹn giờ, hầu như bạn luôn phải làm như sau:

_timer = new Timer( Callback, null, TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );

Điều này sẽ hướng dẫn bộ hẹn giờ chỉ đánh dấu một lần khi khoảng thời gian đã trôi qua. Sau đó, trong chức năng Gọi lại của bạn, bạn Thay đổi bộ hẹn giờ khi công việc đã hoàn thành, không phải trước đó. Thí dụ:

private void Callback( Object state )
{
    // Long running operation
   _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite );
}

Do đó không cần cơ chế khóa vì không có đồng thời. Bộ hẹn giờ sẽ kích hoạt lệnh gọi lại tiếp theo sau khi khoảng thời gian tiếp theo trôi qua + thời gian hoạt động dài.

Nếu bạn cần chạy bộ đếm thời gian của mình ở chính xác N mili giây, thì tôi khuyên bạn nên đo thời gian của hoạt động chạy dài bằng Đồng hồ bấm giờ và sau đó gọi phương thức Thay đổi một cách thích hợp:

private void Callback( Object state )
{
   Stopwatch watch = new Stopwatch();

   watch.Start();
   // Long running operation

   _timer.Change( Math.Max( 0, TIME_INTERVAL_IN_MILLISECONDS - watch.ElapsedMilliseconds ), Timeout.Infinite );
}

Tôi đặc biệt khuyến khích những ai đang làm .NET và đang sử dụng CLR chưa đọc sách của Jeffrey Richter - CLR qua C # , hãy đọc càng sớm càng tốt. Bộ hẹn giờ và nhóm chủ đề được giải thích rất chi tiết ở đó.


6
Tôi không đồng ý với điều đó private void Callback( Object state ) { // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); }. Callbackcó thể được gọi lại trước khi hoàn thành một thao tác.
Alan Coromano

2
Ý tôi là lúc đó Long running operationcó thể mất nhiều thời gian hơn TIME_INTERVAL_IN_MILLISECONDS. Điều gì sẽ xảy ra sau đó?
Alan Coromano

31
Gọi lại sẽ không được gọi lại, đây là vấn đề. Đây là lý do tại sao chúng ta chuyển Timeout.Infinite làm tham số thứ hai. Về cơ bản, điều này có nghĩa là không đánh dấu lại cho bộ hẹn giờ. Sau đó hẹn lại đánh dấu sau khi chúng ta thực hiện xong thao tác.
Ivan Zlatanov

Người mới tham gia luồng ở đây - bạn có nghĩ rằng điều này có thể làm được với ThreadPool, nếu bạn vượt qua bộ đếm thời gian? Tôi đang nghĩ đến một kịch bản trong đó một luồng mới được tạo ra để thực hiện một công việc ở một khoảng thời gian nhất định - và sau đó được đưa xuống nhóm luồng khi hoàn thành.
jedd.ahyoung

2
System.Threading.Timer là một bộ đếm thời gian nhóm luồng, đang thực hiện các lệnh gọi lại của nó trên nhóm luồng, không phải một luồng chuyên dụng. Sau khi bộ đếm thời gian hoàn thành quy trình gọi lại, luồng đã thực hiện lệnh gọi lại sẽ quay trở lại trong nhóm.
Ivan Zlatanov

14

Không cần thiết phải dừng hẹn giờ, hãy xem giải pháp hay từ bài đăng này :

"Bạn có thể để 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 đăng nhập lại của bạn trong Monitor.TryEnter / Exit. Không cần dừng / khởi động lại bộ hẹn giờ trong trường hợp đó; các cuộc gọi chồng chéo sẽ không nhận đượ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);
     }
   }
 }

nó không dành cho trường hợp của tôi. Tôi cần bấm giờ chính xác.
Alan Coromano

Bạn có đang cố gắng ngăn việc nhập cuộc gọi lại một lần nữa không? Nếu không, bạn đang cố gắng đạt được điều gì?
Ivan Leonenko

1. Ngăn chặn việc nhập để gọi lại nhiều lần. 2. Ngăn chặn việc thực thi quá nhiều lần.
Alan Coromano

Đây chính xác là những gì nó làm. # 2 không tốn nhiều chi phí miễn là nó trả về ngay sau câu lệnh if nếu đối tượng bị khóa, đặc biệt nếu bạn có khoảng thời gian lớn như vậy.
Ivan Leonenko

1
Điều này không đảm bảo rằng mã được gọi không ít hơn <interval> sau lần thực thi cuối cùng (một tích tắc mới của bộ đếm thời gian có thể được kích hoạt trong một micro giây sau khi đánh dấu trước đó đã giải phóng khóa). Nó phụ thuộc nếu đây là một yêu cầu nghiêm ngặt hay không (không hoàn toàn rõ ràng từ mô tả của vấn đề).
Marco Mp

9

Sử dụng có System.Threading.Timerbắt buộc không?

Nếu không, System.Timers.Timercó các phương thức Start()và tiện dụng Stop()(và một thuộc AutoResettính bạn có thể đặt thành false, do đó Stop()không cần thiết và bạn chỉ cần gọi Start()sau khi thực thi).


3
Có, nhưng nó có thể là một yêu cầu thực sự, hoặc nó chỉ xảy ra rằng bộ đếm thời gian đã được chọn vì nó là bộ đếm được sử dụng nhiều nhất. Đáng buồn thay, .NET có hàng tấn đối tượng hẹn giờ, chồng chéo lên nhau đến 90% nhưng vẫn khác nhau (đôi khi một cách tinh vi). Tất nhiên, nếu nó là một yêu cầu, giải pháp này hoàn toàn không áp dụng.
Marco Mp

2
Theo tài liệu : Lớp Systems.Timer chỉ có sẵn trong .NET Framework. Nó không có trong Thư viện tiêu chuẩn .NET và không có sẵn trên các nền tảng khác, chẳng hạn như .NET Core hoặc Nền tảng Windows chung. Trên các nền tảng này, cũng như để có thể di động trên tất cả các nền tảng .NET, bạn nên sử dụng lớp System.Threading.Timer để thay thế.
NotAgain nói Hãy phục hồi Monica vào

3

Tôi sẽ chỉ làm:

private static Timer timer;
 private static void Main()
 {
   timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
   Console.ReadLine();
 }

  private static void OnCallBack()
  {
    timer.Dispose();
    Thread.Sleep(3000); //doing some long operation
    timer = new Timer(_ => OnCallBack(), null, 1000 * 10,Timeout.Infinite); //in 10 seconds
  }

Và bỏ qua thông số chu kỳ, vì bạn đang cố gắng tự mình kiểm soát chu kỳ.


Mã ban đầu của bạn đang chạy nhanh nhất có thể, vì bạn tiếp tục chỉ định 0cho dueTimetham số. Từ Timer.Change:

Nếu doTime bằng không (0), phương thức gọi lại được gọi ngay lập tức.


2
Có cần thiết phải xử lý hẹn giờ không? Tại sao bạn không sử dụng Change()phương pháp?
Alan Coromano

21
Bỏ đi bộ đếm thời gian mọi lúc là hoàn toàn không cần thiết và sai.
Ivan Zlatanov

0
 var span = TimeSpan.FromMinutes(2);
 var t = Task.Factory.StartNew(async delegate / () =>
   {
        this.SomeAsync();
        await Task.Delay(span, source.Token);
  }, source.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);

source.Cancel(true/or not);

// or use ThreadPool(whit defaul options thread) like this
Task.Start(()=>{...}), source.Token)

nếu bạn thích sử dụng một số chuỗi vòng lặp bên trong ...

public async void RunForestRun(CancellationToken token)
{
  var t = await Task.Factory.StartNew(async delegate
   {
       while (true)
       {
           await Task.Delay(TimeSpan.FromSeconds(1), token)
                 .ContinueWith(task => { Console.WriteLine("End delay"); });
           this.PrintConsole(1);
        }
    }, token) // drop thread options to default values;
}

// And somewhere there
source.Cancel();
//or
token.ThrowIfCancellationRequested(); // try/ catch block requred.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.