System.Threading.Timer di C # tampaknya tidak berfungsi. Ini berjalan sangat cepat setiap 3 detik

112

Saya memiliki objek pengatur waktu. Saya ingin ini dijalankan setiap menit. Secara khusus, itu harus menjalankan OnCallBackmetode dan menjadi tidak aktif saat OnCallBackmetode sedang berjalan. Setelah OnCallBackmetode selesai, itu (a OnCallBack) memulai ulang pengatur waktu.

Inilah yang saya miliki sekarang:

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
}

Namun, sepertinya itu tidak berhasil. Ini berjalan sangat cepat setiap 3 detik. Bahkan jika menaikkan periode (1000 * 10). Sepertinya itu menutup mata1000 * 10

Apa kesalahan yang telah aku perbuat?

Alan Coromano
sumber
12
Dari Timer.Change: "Jika dueTime adalah nol (0), metode callback akan segera dipanggil.". Sepertinya itu nol bagi saya.
Damien_The_Unbeliever
2
Ya, tapi memangnya kenapa? ada waktunya juga.
Alan Coromano
10
Lalu bagaimana jika ada haid juga? Kalimat yang dikutip tidak membuat klaim tentang nilai periode. Ini hanya mengatakan "jika nilai ini nol, saya akan segera memanggil kembali".
Damien_The_Unbeliever
3
Menariknya jika Anda menyetel dueTime dan periode ke 0, timer akan berjalan setiap detik dan segera dimulai.
Kelvin

Jawaban:

230

Ini bukan penggunaan yang benar dari System.Threading.Timer. Saat Anda memberi contoh Timer, Anda hampir selalu harus melakukan hal berikut:

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

Ini akan menginstruksikan pengatur waktu untuk berdetak hanya sekali ketika jeda telah berlalu. Kemudian dalam fungsi Callback Anda mengubah timer setelah pekerjaan selesai, bukan sebelumnya. Contoh:

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

Jadi tidak perlu mekanisme penguncian karena tidak ada konkurensi. Pengatur waktu akan mengaktifkan panggilan balik berikutnya setelah interval berikutnya telah berlalu + waktu operasi yang berjalan lama.

Jika Anda perlu menjalankan pengatur waktu tepat pada N milidetik, saya sarankan Anda mengukur waktu dari operasi yang berjalan lama menggunakan Stopwatch dan kemudian memanggil metode Ubah dengan tepat:

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 );
}

Saya sangat menganjurkan siapa pun yang melakukan .NET dan menggunakan CLR yang belum membaca buku Jeffrey Richter - CLR via C # , untuk membaca secepat mungkin. Pengatur waktu dan kumpulan benang dijelaskan dengan sangat rinci di sana.

Ivan Zlatanov
sumber
6
Saya tidak setuju dengan itu private void Callback( Object state ) { // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); }. Callbackmungkin dipanggil lagi sebelum operasi selesai.
Alan Coromano
2
Yang saya maksud adalah Long running operationmungkin membutuhkan lebih banyak waktu TIME_INTERVAL_IN_MILLISECONDS. Lalu apa yang akan terjadi?
Alan Coromano
31
Callback tidak akan dipanggil lagi, ini intinya. Inilah mengapa kami melewatkan Timeout.Infinite sebagai parameter kedua. Ini pada dasarnya berarti jangan mencentang lagi untuk pengatur waktu. Kemudian jadwalkan ulang untuk mencentang setelah kami menyelesaikan operasi.
Ivan Zlatanov
Pemula threading di sini - menurut Anda apakah ini mungkin dilakukan dengan ThreadPool, jika Anda memasukkan timer? Saya memikirkan skenario di mana utas baru muncul untuk melakukan pekerjaan pada interval tertentu - dan kemudian diturunkan ke kumpulan utas setelah selesai.
jedd.ahyoung
2
System.Threading.Timer adalah pengatur waktu kumpulan thread, yang menjalankan panggilan baliknya di kumpulan thread, bukan thread khusus. Setelah timer menyelesaikan rutinitas callback, thread yang mengeksekusi callback kembali ke kumpulan.
Ivan Zlatanov
14

Tidak perlu menghentikan timer, lihat solusi bagus dari posting ini :

"Anda dapat membiarkan pengatur waktu terus mengaktifkan metode panggilan balik tetapi membungkus kode non-reentrant Anda di Monitor.TryEnter / Exit. Tidak perlu menghentikan / memulai ulang pengatur waktu dalam hal itu; panggilan yang tumpang tindih tidak akan mendapatkan kunci dan segera kembali."

private void CreatorLoop(object state) 
 {
   if (Monitor.TryEnter(lockObject))
   {
     try
     {
       // Work here
     }
     finally
     {
       Monitor.Exit(lockObject);
     }
   }
 }
Ivan Leonenko
sumber
itu bukan untuk kasusku. Saya harus menghentikan timer dengan tepat.
Alan Coromano
Apakah Anda mencoba untuk mencegah memasukkan panggilan balik lebih dari satu kali? Jika tidak, apa yang ingin Anda capai?
Ivan Leonenko
1. Mencegah masuk ke panggilan balik lebih dari satu kali. 2. Mencegah mengeksekusi terlalu banyak waktu.
Alan Coromano
Inilah tepatnya yang dilakukannya. # 2 tidak terlalu banyak overhead asalkan kembali tepat setelah pernyataan if jika objek terkunci, terutama jika Anda memiliki interval yang besar.
Ivan Leonenko
1
Ini tidak menjamin bahwa kode tersebut dipanggil tidak kurang dari <interval> setelah eksekusi terakhir (tanda baru pengatur waktu dapat diaktifkan satu mikrodetik setelah tanda centang sebelumnya membuka kunci). Itu tergantung apakah ini merupakan persyaratan ketat atau tidak (tidak sepenuhnya jelas dari uraian masalah).
Marco Mp
9

Apakah menggunakan System.Threading.Timerwajib?

Jika tidak, System.Timers.Timermemiliki handy Start()dan Stop()metode (dan AutoResetproperti yang dapat Anda setel ke false, sehingga Stop()tidak diperlukan dan Anda cukup memanggil Start()setelah menjalankan).

Marco Mp
sumber
3
Ya, tetapi ini bisa menjadi persyaratan nyata, atau kebetulan timer dipilih karena yang paling banyak digunakan. Sayangnya .NET memiliki banyak objek pengatur waktu, tumpang tindih selama 90% tetapi masih (terkadang secara halus) berbeda. Tentu saja, jika itu merupakan persyaratan, solusi ini tidak berlaku sama sekali.
Marco Mp
2
Sesuai dokumentasi : Kelas Systems.Timer hanya tersedia di .NET Framework. Itu tidak termasuk dalam .NET Standard Library dan tidak tersedia di platform lain, seperti .NET Core atau Universal Windows Platform. Pada platform ini, serta untuk portabilitas di semua platform .NET, Anda harus menggunakan kelas System.Threading.Timer sebagai gantinya.
NotAgain mengatakan Reinstate Monica
3

Saya hanya akan melakukan:

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
  }

Dan abaikan parameter periode, karena Anda mencoba untuk mengontrol periodicy sendiri.


Kode asli Anda berjalan secepat mungkin, karena Anda tetap menentukan 0untuk dueTimeparameter. Dari Timer.Change:

Jika dueTime nol (0), metode callback dipanggil segera.

Damien_The_Tidak percaya
sumber
2
Apakah perlu membuang pengatur waktu? Mengapa Anda tidak menggunakan Change()metode?
Alan Coromano
21
Membuang pengatur waktu setiap kali sama sekali tidak perlu dan salah.
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)

jika Anda suka menggunakan beberapa utas lingkaran di dalam ...

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.
Umka
sumber