ตัวจับเวลา C # ผ่านไปในเธรดแยกหรือไม่?


100

System.Timers.Timer ผ่านเธรดที่แยกจากเธรดที่สร้างขึ้นหรือไม่

สมมติว่าฉันมีคลาสที่มีตัวจับเวลาที่ยิงทุกๆ 5 วินาที เมื่อตัวจับเวลาเริ่มทำงานในวิธีการที่ผ่านไปวัตถุบางอย่างจะถูกแก้ไข สมมติว่าใช้เวลาแก้ไขวัตถุนี้นานเช่น 10 วินาที เป็นไปได้ไหมที่ฉันจะประสบปัญหาการชนกันของเธรดในสถานการณ์นี้


ซึ่งอาจนำไปสู่ปัญหา โปรดทราบว่าโดยทั่วไปเธรด threadpool ไม่ได้ออกแบบมาสำหรับกระบวนการที่รันเป็นเวลานาน
Greg D

ฉันพบสิ่งนี้ทดสอบกับบริการ windows สิ่งที่ได้ผลสำหรับฉันคือการปิดใช้งานตัวจับเวลาเป็นคำสั่งแรกในเหตุการณ์ OnTimer ทำงานของฉันจากนั้นเปิดใช้งานตัวจับเวลาในตอนท้าย สิ่งนี้ทำงานได้อย่างน่าเชื่อถือในสภาพแวดล้อมการผลิตมาระยะหนึ่ง
Steve

คำตอบ:


62

สำหรับSystem.Timers.Timer :

ดูคำตอบของ Brian Gideon ด้านล่าง

สำหรับSystem.Threading.Timer :

เอกสาร MSDN เกี่ยวกับสถานะตัวจับเวลา :

คลาส System.Threading.Timer ทำการเรียกกลับบนเธรด ThreadPoolและไม่ใช้โมเดลเหตุการณ์เลย

ดังนั้นตัวจับเวลาจึงผ่านไปในเธรดอื่น


21
จริง แต่นั่นเป็นคลาสที่แตกต่างกันอย่างสิ้นเชิง OP ถามเกี่ยวกับคลาส System.Timers.Timer
Brian Gideon

1
โอ้คุณพูดถูก msdn.microsoft.com/en-us/library/system.timers.timer.aspxระบุว่า "เหตุการณ์ที่ผ่านไปถูกยกขึ้นบนเธรด ThreadPool" ฉันคิดว่าข้อสรุปเดียวกันจากที่นั่น
Joren

6
ใช่ แต่มันไม่ง่ายอย่างนั้น ดูคำตอบของฉัน
Brian Gideon

192

มันขึ้นอยู่กับ. System.Timers.Timerมีสองโหมดของการดำเนินงาน

หากSynchronizingObjectตั้งค่าเป็นISynchronizeInvokeอินสแตนซ์Elapsedเหตุการณ์จะดำเนินการบนเธรดที่โฮสต์วัตถุซิงโครไนซ์ โดยปกติแล้วISynchronizeInvokeอินสแตนซ์เหล่านี้จะไม่มีใครอื่นนอกจากอินสแตนซ์แบบเก่าControlและFormอินสแตนซ์ที่เราคุ้นเคย ดังนั้นในกรณีนี้Elapsedเหตุการณ์จะถูกเรียกใช้บนเธรด UI และทำงานคล้ายกับไฟล์System.Windows.Forms.Timer. มิฉะนั้นจะขึ้นอยู่กับISynchronizeInvokeอินสแตนซ์เฉพาะที่ใช้

ถ้าSynchronizingObjectเป็นโมฆะElapsedเหตุการณ์จะถูกเรียกใช้บนThreadPoolเธรดและจะทำงานคล้ายกับSystem.Threading.Timer. ในความเป็นจริงมันใช้System.Threading.Timerเบื้องหลังและดำเนินการมาร์แชลหลังจากได้รับการโทรกลับของตัวจับเวลาหากจำเป็น


4
หากคุณต้องการให้ตัวจับเวลาเรียกกลับเพื่อดำเนินการกับเธรดใหม่คุณควรใช้System.Threading.TimerหรือSystem.Timers.Timer?
CJ7

1
@ cj7: อย่างใดอย่างหนึ่งสามารถทำได้
Brian Gideon

และถ้าฉันมีรายชื่อประเภทที่ซับซ้อน (บุคคล) และต้องการมีเวลาในแต่ละคน? ฉันต้องการให้สิ่งนี้ทำงานบนเธรดเดียวกัน (ทุกคน) เพราะถ้ามันเรียกใช้เมธอดบุคคลที่หนึ่งบุคคลที่สองจะต้องรอจนกว่าเหตุการณ์แรกจะสิ้นสุดเหตุการณ์ที่ผ่านไป ฉันสามารถทำได้หรือไม่?
Leandro De Mello Fagundes

4
ทุกคน ... System.Timers.Timerมีโหมดการทำงานสองโหมด สามารถรันบนเธรดพูลเธรดที่กำหนดแบบสุ่มหรือสามารถรันบนเธรดใด ๆ ที่โฮสต์ISynchronizeInvokeอินสแตนซ์ ฉันไม่รู้ว่าจะทำให้ชัดเจนกว่านี้ได้อย่างไร System.Threading.Timerมีเพียงเล็กน้อย (ถ้ามี) ที่เกี่ยวข้องกับคำถามเดิม
Brian Gideon

@LeandroDeMelloFagundes ใช้แบบlockนั้นไม่ได้เหรอ
Ozkan

24

เหตุการณ์ที่ผ่านไปแต่ละเหตุการณ์จะเริ่มทำงานในเธรดเดียวกันเว้นแต่ว่า Elapsed ก่อนหน้านี้ยังคงทำงานอยู่

ดังนั้นจึงจัดการการชนให้คุณ

ลองใส่ลงในคอนโซล

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

คุณจะได้รับสิ่งนี้

10
6
12
6
12

โดยที่ 10 คือเธรดการโทรและ 6 และ 12 กำลังเริ่มทำงานจากเหตุการณ์ที่ผ่านไป bg หากคุณลบ Thread.Sleep (2000); คุณจะได้รับสิ่งนี้

10
6
6
6
6

เนื่องจากไม่มีการชนกัน

แต่สิ่งนี้ยังทำให้คุณมีปัญหา หากคุณเริ่มต้นเหตุการณ์ทุกๆ 5 วินาทีและใช้เวลา 10 วินาทีในการแก้ไขคุณต้องมีการล็อกเพื่อข้ามการแก้ไขบางอย่าง


8
การเพิ่มtimer.Stop()ที่จุดเริ่มต้นของวิธีเหตุการณ์ที่ผ่านไปแล้วtimer.Start()เมื่อสิ้นสุดวิธีเหตุการณ์ที่ผ่านไปจะทำให้เหตุการณ์ที่ผ่านไปไม่ชนกัน
Metro Smurf

4
คุณไม่จำเป็นต้องใส่ timer.Stop () คุณเพียงแค่กำหนด timer.AutoReset = false; จากนั้นให้คุณจับเวลาเริ่ม () หลังจากที่คุณประมวลผลเหตุการณ์ ฉันคิดว่านี่เป็นวิธีที่ดีกว่าที่คุณจะหลีกเลี่ยงการชนกัน
João Antunes

17

สำหรับ System.Timers.Timer บนเธรดที่แยกจากกันหากไม่ได้ตั้งค่า SynchronizingObject

    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 วินาทีหรือไม่:

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

ดังที่เห็น OnDummyTimerFired ถูกดำเนินการบนเธรดของ Workers

ไม่ความซับซ้อนเพิ่มเติม - ถ้าคุณลดช่วงเวลาเพื่อพูด 10 มิลลิวินาที

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

เนื่องจากหากการดำเนินการก่อนหน้าของ OnDummyTimerFired ไม่เสร็จสิ้นเมื่อขีดต่อไปถูกเริ่มทำงาน. NET จะสร้างเธรดใหม่เพื่อทำงานนี้

สร้างความซับซ้อนให้มากขึ้น"คลาส System.Timers.Timer เป็นวิธีง่ายๆในการจัดการกับภาวะที่กลืนไม่เข้าคายไม่ออกนี้ - มันแสดงคุณสมบัติ SynchronizingObject สาธารณะการตั้งค่าคุณสมบัตินี้เป็นอินสแตนซ์ของฟอร์ม Windows (หรือตัวควบคุมบนฟอร์ม Windows) จะช่วยให้มั่นใจได้ว่า ว่าโค้ดในตัวจัดการเหตุการณ์ Elapsed ของคุณทำงานบนเธรดเดียวกันกับที่ SynchronizingObject สร้างอินสแตนซ์ "

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


นั่นเป็นตัวอย่างที่ดี จนถึงตอนนี้ฉันไม่รู้และฉันต้องตั้งค่าล็อคที่นี่และที่นั่น สมบูรณ์แบบ.
Olaru Mircea

และจะเกิดอะไรขึ้นถ้าฉันต้องการให้ตัวจับเวลาเริ่มเหตุการณ์ที่ผ่านไปในเธรดหลักของแอปพลิเคชันคอนโซล
jacktric

13

หากเหตุการณ์ที่ผ่านไปใช้เวลานานขึ้นจากนั้นช่วงเวลาดังกล่าวจะสร้างเธรดอื่นเพื่อเพิ่มเหตุการณ์ที่ผ่านไป แต่มีวิธีแก้ปัญหานี้

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

+1 คำตอบที่เรียบง่ายและชัดเจน แต่จะไม่ได้ผลเสมอไป ควรใช้คุณสมบัติ lock หรือ SynchronizingObject ของตัวจับเวลา
RollerCosta
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.