Apakah C # Timer berlalu di utas terpisah?

98

Apakah System.Timers.Timer berlalu pada utas terpisah dari utas yang membuatnya?

Katakanlah saya memiliki kelas dengan pengatur waktu yang menyala setiap 5 detik. Saat pengatur waktu menyala, dalam metode yang telah berlalu, beberapa objek diubah. Katakanlah butuh waktu lama untuk memodifikasi objek ini, seperti 10 detik. Apakah mungkin saya akan mengalami benturan thread dalam skenario ini?

pengguna113164
sumber
Ini bisa menimbulkan masalah. Perhatikan bahwa, secara umum, utas threadpool tidak dirancang untuk proses yang berjalan lama.
Greg D
Saya mengalami ini, menguji dengan layanan windows. Apa yang berhasil bagi saya adalah menonaktifkan pengatur waktu sebagai instruksi pertama dalam acara OnTimer, melakukan tugas-tugas saya, kemudian mengaktifkan pengatur waktu di akhir. Ini telah bekerja dengan andal dalam lingkungan produksi selama beberapa waktu.
Steve

Jawaban:

60

Untuk System.Timers.Timer :

Lihat jawaban Brian Gideon di bawah ini

Untuk System.Threading.Timer :

Dokumentasi MSDN tentang Pengatur Waktu menyatakan:

Kelas System.Threading.Timer membuat panggilan balik pada utas ThreadPool dan tidak menggunakan model kejadian sama sekali.

Jadi memang pengatur waktu berlalu di utas yang berbeda.

Joren
sumber
21
Benar, tapi itu kelas yang sama sekali berbeda. OP bertanya tentang kelas System.Timers.Timer.
Brian Gideon
1
Oh, kamu benar. msdn.microsoft.com/en-us/library/system.timers.timer.aspx mengatakan "Peristiwa yang berlalu dimunculkan di thread ThreadPool." Kesimpulan yang sama dari sana saya kira.
Joren
6
Ya, tapi tidak sesederhana itu. Lihat jawaban saya.
Brian Gideon
192

Tergantung. Ini System.Timers.Timermemiliki dua mode operasi.

Jika SynchronizingObjectdisetel ke sebuah ISynchronizeInvokeinstance, maka Elapsedacara tersebut akan dijalankan di thread yang menghosting objek sinkronisasi. Biasanya ISynchronizeInvokecontoh ini tidak lain adalah contoh lama Controldan Formcontoh yang kita semua kenal. Jadi dalam kasus ini, Elapsedperistiwa dipanggil pada UI thread dan berperilaku mirip dengan System.Windows.Forms.Timer. Jika tidak, itu sangat tergantung pada ISynchronizeInvokecontoh spesifik yang digunakan.

Jika SynchronizingObjectnull maka Elapsedacara tersebut dipanggil pada aThreadPool thread dan berperilaku mirip dengan System.Threading.Timer. Faktanya, itu benar-benar menggunakan di System.Threading.Timerbelakang layar dan melakukan operasi marsaling setelah menerima panggilan balik pengatur waktu jika diperlukan.

Brian Gideon
sumber
4
Jika Anda ingin callback timer dijalankan pada thread baru, haruskah Anda menggunakan System.Threading.Timeratau System.Timers.Timer?
CJ7
1
@ cj7: Salah satu dapat melakukan itu.
Brian Gideon
dan jika saya memiliki daftar tipe kompleks (orang) dan ingin memiliki waktu di dalam setiap orang? Saya perlu ini berjalan di utas yang sama (semua orang), karena jika memanggil metode orang pertama, orang kedua harus menunggu sampai yang pertama mengakhiri acara yang telah berlalu. Bolehkah saya melakukan itu
Leandro De Mello Fagundes
4
Setiap orang ... System.Timers.Timermemiliki dua mode operasi. Ini dapat berjalan pada utas kumpulan utas yang ditetapkan secara acak ATAU dapat berjalan di utas apa pun yang menghosting ISynchronizeInvokeinstance. Saya tidak tahu bagaimana membuatnya lebih jelas. System.Threading.Timertidak ada hubungannya (jika ada) dengan pertanyaan awal.
Brian Gideon
@LeandroDeMelloFagundes Tidak bisakah Anda menggunakan lockuntuk itu?
Ozkan
24

Setiap peristiwa yang telah berlalu akan diaktifkan di utas yang sama kecuali yang Berlalu sebelumnya masih berjalan.

Jadi itu menangani tabrakan untuk Anda

coba letakkan ini di konsol

static void Main(string[] args)
{
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
    var timer = new Timer(1000);
    timer.Elapsed += timer_Elapsed;
    timer.Start();
    Console.ReadLine();
}

static void timer_Elapsed(object sender, ElapsedEventArgs e)
{
    Thread.Sleep(2000);
    Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);
}

Anda akan mendapatkan sesuatu seperti ini

10
6
12
6
12

di mana 10 adalah utas panggilan dan 6 dan 12 diaktifkan dari kejadian bg berlalu. Jika Anda menghapus Thread.Sleep (2000); Anda akan mendapatkan sesuatu seperti ini

10
6
6
6
6

Karena tidak ada tabrakan.

Tapi ini masih membuatmu bermasalah. jika Anda menjalankan acara setiap 5 detik dan perlu 10 detik untuk mengedit, Anda perlu penguncian untuk melewati beberapa pengeditan.

Simon
sumber
8
Menambahkan timer.Stop()di awal metode kejadian berlalu dan kemudian timer.Start()di akhir metode kejadian berlalu akan menjaga kejadian berlalu dari bertabrakan.
Metro Smurf
4
Anda tidak perlu meletakkan timer.Stop () Anda hanya perlu mendefinisikan timer.AutoReset = false; kemudian Anda membuat timer.Start () setelah Anda memproses acara tersebut. Saya pikir ini adalah cara yang lebih baik untuk menghindari tabrakan.
João Antunes
17

Untuk System.Timers.Timer, di utas terpisah, jika SynchronizingObject tidak disetel.

    static System.Timers.Timer DummyTimer = null;

    static void Main(string[] args)
    {
        try
        {

            Console.WriteLine("Main Thread Id: " + System.Threading.Thread.CurrentThread.ManagedThreadId);

            DummyTimer = new System.Timers.Timer(1000 * 5); // 5 sec interval
            DummyTimer.Enabled = true;
            DummyTimer.Elapsed += new System.Timers.ElapsedEventHandler(OnDummyTimerFired);
            DummyTimer.AutoReset = true;

            DummyTimer.Start();

            Console.WriteLine("Hit any key to exit");
            Console.ReadLine();
        }
        catch (Exception Ex)
        {
            Console.WriteLine(Ex.Message);
        }

        return;
    }

    static void OnDummyTimerFired(object Sender, System.Timers.ElapsedEventArgs e)
    {
        Console.WriteLine(System.Threading.Thread.CurrentThread.ManagedThreadId);
        return;
    }

Output Anda akan melihat jika DummyTimer diaktifkan pada interval 5 detik:

Main Thread Id: 9
   12
   12
   12
   12
   12
   ... 

Jadi, seperti yang terlihat, OnDummyTimerFired dijalankan pada thread Pekerja.

Tidak, komplikasi lebih lanjut - Jika Anda mengurangi interval menjadi 10 ms,

Main Thread Id: 9
   11
   13
   12
   22
   17
   ... 

Ini karena jika eksekusi prev dari OnDummyTimerFired tidak dilakukan saat centang berikutnya diaktifkan, maka .NET akan membuat utas baru untuk melakukan pekerjaan ini.

Lebih rumit lagi, "Kelas System.Timers.Timer menyediakan cara mudah untuk menangani dilema ini — ini memperlihatkan properti SynchronizingObject publik. Menyetel properti ini ke instance Formulir Windows (atau kontrol pada Formulir Windows) akan memastikan bahwa kode dalam penangan kejadian Berlalu Anda berjalan pada utas yang sama dengan tempat pembuatan instance SynchronizingObject. "

http://msdn.microsoft.com/en-us/magazine/cc164015.aspx#S2

Swab Jat
sumber
Itu contoh yang bagus. Saya tidak tahu sampai sekarang dan saya perlu memasang beberapa kunci di sana-sini. Sempurna.
Olaru Mircea
Dan bagaimana jika saya ingin pengatur waktu mengaktifkan peristiwa yang telah berlalu di thread utama aplikasi konsol?
jacktric
13

Jika acara yang telah berlalu membutuhkan waktu lebih lama dari intervalnya, itu akan membuat utas lain untuk memunculkan acara yang telah berlalu. Tetapi ada solusi untuk ini

static void timer_Elapsed(object sender, ElapsedEventArgs e)    
{     
   try
   {
      timer.Stop(); 
      Thread.Sleep(2000);        
      Debug.WriteLine(Thread.CurrentThread.ManagedThreadId);    
   }
   finally
   {
     timer.Start();
   }
}
Rajan
sumber
+1 Jawaban sederhana dan bersih, tetapi ini tidak akan selalu berhasil. Sebaliknya seseorang harus menggunakan kunci atau properti SynchronizingObject timer.
RollerCosta