C # Zamanlayıcıları ayrı bir iş parçacığında mı geçiyor?


100

System.Timers.Timer kendisini oluşturan iş parçacığından ayrı bir iş parçacığında geçiyor mu?

Diyelim ki her 5 saniyede bir ateşleyen zamanlayıcıya sahip bir sınıfım var. Zamanlayıcı tetiklendiğinde, geçen yöntemde bazı nesneler değiştirilir. Bu nesneyi değiştirmenin 10 saniye gibi uzun zaman aldığını söyleyelim. Bu senaryoda iş parçacığı çakışmalarıyla karşılaşmam mümkün mü?


Bu sorunlara yol açabilir. Genel olarak, iş parçacığı iş parçacıklarının uzun süre çalışan işlemler için tasarlanmadığını unutmayın.
Greg D

Windows servisiyle test ederek bununla karşılaşıyordum. Benim için işe yarayan şey, zamanlayıcıyı OnTimer etkinliğinde ilk talimat olarak devre dışı bırakmak, görevlerimi gerçekleştirmek ve ardından zamanlayıcıyı sonunda etkinleştirmektir. Bu, bir süredir üretim ortamında güvenilir bir şekilde çalıştı.
Steve

Yanıtlar:


62

İçin System.Timers.Timer :

Bkz aşağıda Brian Gideon cevabı

İçin System.Threading.Timer :

Zamanlayıcılarla ilgili MSDN Belgeleri şunları belirtir:

System.Threading.Timer sınıfı , bir ThreadPool iş parçacığında geri çağırmalar yapar ve olay modelini hiç kullanmaz.

Yani gerçekten de zamanlayıcı farklı bir iş parçacığında sona erer.


21
Doğru, ama bu tamamen farklı bir sınıf. OP, System.Timers.Timer sınıfını sordu.
Brian Gideon

1
Oh, haklısın. msdn.microsoft.com/en-us/library/system.timers.timer.aspx "Elapsed olayı bir ThreadPool iş parçacığında başlatıldı" diyor. Sanırım oradan aynı sonuç.
Joren

6
Evet, ama o kadar basit değil. Cevabımı gör.
Brian Gideon

192

Değişir. System.Timers.Timerİki çalışma modu vardır.

Bir örneğe SynchronizingObjectayarlanırsa ISynchronizeInvoke, Elapsedolay, senkronizasyon nesnesini barındıran evre üzerinde yürütülür. Genellikle bu ISynchronizeInvokeörnekler tamamen eski Controlve Formaşina olduğumuz örneklerden başkası değildir . Bu durumda, Elapsedolay UI iş parçacığında çağrılır ve System.Windows.Forms.Timer. Aksi takdirde, gerçekten ISynchronizeInvokekullanılan belirli örneğe bağlıdır .

Eğer SynchronizingObjectis sonra boş Elapsedolayın üzerine çağrılır ThreadPooliplik ve benzer davranır System.Threading.Timer. Aslında, aslında System.Threading.Timerperde arkasında bir kullanıyor ve gerekirse zamanlayıcı geri aramasını aldıktan sonra sıralama işlemini gerçekleştiriyor .


4
Zamanlayıcı geri aramasının yeni bir iş parçacığında yürütülmesini istiyorsanız, bir System.Threading.Timerveya System.Timers.Timer?
CJ7

1
@ cj7: Bunu biri yapabilir.
Brian Gideon

ve karmaşık türden (kişi) oluşan bir listem varsa ve her insanın içinde bir zaman geçirmek istersem? Bunun aynı iş parçacığında (tüm kişiler) çalışmasına ihtiyacım var, çünkü eğer birinci kişi yöntemini çağırırsa, ikinci kişi birinci geçen olayı bitirene kadar beklemelidir. Bunu yapabilir miyim?
Leandro De Mello Fagundes

4
Herkesin ... System.Timers.Timeriki çalışma modu vardır. Rastgele atanan bir iş parçacığı havuzu iş parçacığı üzerinde çalışabilir VEYA ISynchronizeInvokeörneği barındıran iş parçacığı üzerinde çalışabilir . Bunu nasıl daha açık hale getireceğimi bilmiyorum. System.Threading.Timerorijinal soruyla çok az (eğer varsa) ilgisi vardır.
Brian Gideon

@LeandroDeMelloFagundes Bunun lockiçin kullanamaz mısın?
Özkan

24

Önceki Elapsed hala çalışmadıkça, her geçen olay aynı iş parçacığında tetiklenir.

Böylece çarpışmayı sizin için halleder

bunu bir konsola koymayı dene

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

bunun gibi bir şey alacaksın

10
6
12
6
12

burada 10 çağıran iş parçacığıdır ve 6 ve 12 bg geçen olaydan ateşlenmektedir. Thread.Sleep (2000) kaldırırsanız; bunun gibi bir şey alacaksın

10
6
6
6
6

Çarpışma olmadığı için.

Ama bu hala sizi bir sorunla karşı karşıya bırakıyor. Olayı her 5 saniyede bir çalıştırıyorsanız ve düzenlemeniz 10 saniye sürüyorsa, bazı düzenlemeleri atlamak için biraz kilitlemeye ihtiyacınız vardır.


8
timer.Stop()Elapsed olay yönteminin başına bir ve ardından Elapsed olay yönteminin timer.Start()sonuna bir eklenmesi , Elapsed olayının çakışmasını engeller.
Metro Smurf

4
Timer.Stop () koymanıza gerek yok, sadece timer.AutoReset = false; ardından olayı işledikten sonra timer.Start () yaparsınız. Bunun çarpışmalardan kaçınmanın daha iyi bir yolu olduğunu düşünüyorum.
João Antunes

17

SynchronizingObject ayarlanmamışsa, ayrı iş parçacığında System.Timers.Timer için.

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

DummyTimer 5 saniye aralıklarla tetiklendiğinde göreceğiniz çıktı:

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

Görüldüğü gibi OnDummyTimerFired, Workers iş parçacığında çalıştırılır.

Hayır, daha fazla karmaşıklık - Aralığı 10 ms olarak kısaltırsanız,

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

Bunun nedeni, OnDummyTimerFired'ın önceki çalıştırma işleminin bir sonraki tick çalıştırıldığında yapılmaması durumunda, .NET'in bu işi yapmak için yeni bir iş parçacığı oluşturmasıdır.

İşleri daha da karmaşık hale getiren "System.Timers.Timer sınıfı, bu ikilemle başa çıkmak için kolay bir yol sağlar - genel bir SynchronizingObject özelliğini ortaya çıkarır. Bu özelliği bir Windows Form örneğine (veya bir Windows Formundaki bir kontrole) ayarlamak, Elapsed olay işleyicinizdeki kodun, SynchronizingObject öğesinin başlatıldığı aynı iş parçacığında çalıştığını.

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


Bu iyi bir örnek. Şimdiye kadar bilmiyordum ve ara sıra bazı kilitler kurmam gerekiyor. Mükemmel.
Olaru Mircea

Peki ya zamanlayıcının Elapsed olayını bir konsol uygulamasının ana iş parçacığında tetiklemesini istersem?
jacktric

13

Eğer geçen olay aralıktan daha uzun sürerse, geçen olayı yükseltmek için başka bir iş parçacığı yaratacaktır. Ancak bunun için bir çözüm var

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

+1 Basit ve net cevap, ancak bu her zaman işe yaramayacak. Aksine, timer'ın lock veya SynchronizingObject özelliğini kullanmalıdır.
RollerCosta
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.