System.Threading.Timer ใน C # ดูเหมือนว่าจะไม่ทำงาน มันทำงานเร็วมากทุกๆ 3 วินาที


112

ฉันเป็นวัตถุจับเวลา ฉันต้องการให้มันทำงานทุกนาที โดยเฉพาะอย่างยิ่งควรเรียกใช้OnCallBackวิธีการและไม่ได้ใช้งานในขณะที่OnCallBackวิธีการทำงาน เมื่อOnCallBackวิธีการเสร็จสิ้น (a OnCallBack) จะเริ่มการจับเวลาใหม่

นี่คือสิ่งที่ฉันมีในตอนนี้:

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
}

อย่างไรก็ตามดูเหมือนว่าจะไม่ทำงาน มันทำงานเร็วมากทุกๆ 3 วินาที แม้ว่าจะเพิ่มระยะเวลา (1,000 * 10) ดูเหมือนว่ามันจะเมิน1000 * 10

ฉันทำอะไรผิด?


12
From Timer.Change: "ถ้า dueTime เป็นศูนย์ (0) วิธีการโทรกลับจะถูกเรียกใช้ทันที" ดูเหมือนว่าจะเป็นศูนย์สำหรับฉัน
Damien_The_Unbeliever

2
ใช่ แต่แล้วไง? นอกจากนี้ยังมีช่วงเวลา
Alan Coromano

10
แล้วถ้ามีช่วงเวลาด้วยล่ะ? ประโยคที่ยกมาไม่มีการอ้างสิทธิ์เกี่ยวกับค่างวด มันบอกว่า "ถ้าค่านี้เป็นศูนย์ฉันจะเรียกกลับทันที"
Damien_The_Unbeliever

3
น่าสนใจถ้าคุณตั้งค่าทั้ง dueTime และ period เป็น 0 ตัวจับเวลาจะทำงานทุกวินาทีและเริ่มทันที
Kelvin

คำตอบ:


230

นี่ไม่ใช่การใช้งาน System.Threading.Timer ที่ถูกต้อง เมื่อคุณสร้างอินสแตนซ์ตัวจับเวลาคุณควรทำสิ่งต่อไปนี้เกือบตลอดเวลา:

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

สิ่งนี้จะสั่งให้ตัวจับเวลาทำเครื่องหมายเพียงครั้งเดียวเมื่อพ้นช่วงเวลาดังกล่าว จากนั้นในฟังก์ชั่นการโทรกลับของคุณคุณจะเปลี่ยนตัวจับเวลาเมื่องานเสร็จสิ้นไม่ใช่ก่อนหน้านี้ ตัวอย่าง:

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

ดังนั้นจึงไม่จำเป็นต้องมีกลไกการล็อคเนื่องจากไม่มีการทำงานพร้อมกัน ตัวจับเวลาจะเริ่มการโทรกลับครั้งต่อไปหลังจากผ่านช่วงเวลาถัดไป + เวลาของการดำเนินการที่ยาวนาน

หากคุณต้องการเรียกใช้ตัวจับเวลาที่ N มิลลิวินาทีแน่นอนฉันขอแนะนำให้คุณวัดเวลาของการทำงานที่ยาวนานโดยใช้นาฬิกาจับเวลาแล้วเรียกใช้วิธีการเปลี่ยนอย่างเหมาะสม:

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

ฉันขอแนะนำให้ทุกคนที่ทำ. NET และใช้ CLR ที่ยังไม่ได้อ่านหนังสือของ Jeffrey Richter - CLR ผ่าน C #ให้อ่านโดยเร็วที่สุด มีการอธิบายตัวจับเวลาและเธรดพูลอย่างละเอียด


6
private void Callback( Object state ) { // Long running operation _timer.Change( TIME_INTERVAL_IN_MILLISECONDS, Timeout.Infinite ); }ผมไม่เห็นด้วยกับที่ Callbackอาจถูกเรียกอีกครั้งก่อนที่การดำเนินการจะเสร็จสิ้น
Alan Coromano

2
สิ่งที่ฉันหมายถึงคือตอนนั้นLong running operationอาจต้องใช้เวลามากกว่าTIME_INTERVAL_IN_MILLISECONDSนี้ จะเกิดอะไรขึ้น?
Alan Coromano

32
จะไม่โทรกลับอีกนี่คือประเด็น นี่คือสาเหตุที่เราผ่าน Timeout ไม่มีที่สิ้นสุดเป็นพารามิเตอร์ที่สอง โดยทั่วไปหมายความว่าอย่าทำเครื่องหมายอีกครั้งสำหรับตัวจับเวลา จากนั้นกำหนดเวลาใหม่เพื่อติ๊กหลังจากที่เราดำเนินการเสร็จสิ้น
Ivan Zlatanov

มือใหม่หัดทำเธรดที่นี่ - คุณคิดว่านี่เป็นไปได้ไหมที่จะทำกับ a ThreadPoolถ้าคุณผ่านในตัวจับเวลา? ฉันกำลังคิดถึงสถานการณ์ที่เธรดใหม่ถูกสร้างขึ้นเพื่อทำงานในช่วงเวลาหนึ่ง - จากนั้นก็ย้ายไปที่เธรดพูลเมื่อเสร็จสมบูรณ์
jedd.ahyoung

2
System.Threading.Timer คือตัวจับเวลาเธรดพูลซึ่งกำลังดำเนินการเรียกกลับบนเธรดพูลไม่ใช่เธรดเฉพาะ หลังจากตัวจับเวลาเสร็จสิ้นขั้นตอนการโทรกลับเธรดที่ดำเนินการเรียกกลับจะย้อนกลับในพูล
Ivan Zlatanov

14

ไม่จำเป็นต้องหยุดตัวจับเวลาดูวิธีแก้ปัญหาที่ดีจากโพสต์นี้ :

"คุณสามารถปล่อยให้ตัวจับเวลาเริ่มต้นวิธีการโทรกลับต่อไป แต่ห่อรหัสที่ไม่กลับเข้าใหม่ของคุณไว้ในจอภาพลองป้อน / ออกไม่จำเป็นต้องหยุด / เริ่มตัวจับเวลาใหม่ในกรณีนั้นการโทรที่ทับซ้อนกันจะไม่ได้รับการล็อกและกลับทันที"

private void CreatorLoop(object state) 
 {
   if (Monitor.TryEnter(lockObject))
   {
     try
     {
       // Work here
     }
     finally
     {
       Monitor.Exit(lockObject);
     }
   }
 }

ไม่ใช่สำหรับกรณีของฉัน ฉันต้องหยุดตัวจับเวลาเป๊ะ ๆ
Alan Coromano

คุณกำลังพยายามป้องกันไม่ให้มีการโทรกลับมากกว่าหนึ่งครั้งหรือไม่? หากไม่มีคุณพยายามทำอะไรเพื่อให้บรรลุ?
Ivan Leonenko

1. ป้องกันการเข้าเพื่อโทรกลับมากกว่าหนึ่งครั้ง 2. ป้องกันการดำเนินการหลายครั้งเกินไป
Alan Coromano

นี่คือสิ่งที่มันทำ # 2 มีค่าใช้จ่ายไม่มากนักตราบเท่าที่ส่งกลับทันทีหลังจากคำสั่ง if หากวัตถุถูกล็อคโดยเฉพาะอย่างยิ่งถ้าคุณมีช่วงเวลาที่มาก
Ivan Leonenko

1
สิ่งนี้ไม่ได้รับประกันว่ารหัสจะถูกเรียกไม่น้อยกว่า <interval> หลังจากการดำเนินการครั้งล่าสุด (ขีดใหม่ของตัวจับเวลาสามารถยิงได้ในระดับไมโครวินาทีหลังจากที่ขีดก่อนหน้าปลดล็อค) ขึ้นอยู่กับว่านี่เป็นข้อกำหนดที่เข้มงวดหรือไม่ (ไม่ชัดเจนทั้งหมดจากคำอธิบายของปัญหา)
Marco Mp

9

System.Threading.Timerบังคับใช้หรือไม่

หากไม่เป็นเช่นSystem.Timers.Timerนั้นมีประโยชน์Start()และStop()วิธีการ (และAutoResetคุณสมบัติที่คุณสามารถตั้งค่าเป็นเท็จเพื่อที่Stop()ไม่จำเป็นต้องใช้และคุณเพียงแค่เรียกใช้Start()หลังจากดำเนินการ)


3
ใช่ แต่อาจเป็นความต้องการที่แท้จริงหรือเพิ่งเกิดขึ้นเมื่อมีการเลือกตัวจับเวลาเนื่องจากเป็นตัวจับเวลาที่ใช้บ่อยที่สุด น่าเสียดายที่. NET มีวัตถุจับเวลามากมายทับซ้อนกันถึง 90% แต่ก็ยังแตกต่างกัน (บางครั้งก็ละเอียด) แน่นอนว่าหากเป็นข้อกำหนดการแก้ปัญหานี้ใช้ไม่ได้เลย
Marco Mp

2
ตามเอกสารประกอบ : คลาส Systems.Timer พร้อมใช้งานใน. NET Framework เท่านั้น ไม่รวมอยู่ใน. NET Standard Library และไม่มีให้ใช้งานบนแพลตฟอร์มอื่นเช่น. NET Core หรือ Universal Windows Platform บนแพลตฟอร์มเหล่านี้เช่นเดียวกับการพกพาข้ามแพลตฟอร์ม. NET ทั้งหมดคุณควรใช้คลาส System.Threading.Timer แทน
NotAgain กล่าวว่า Reinstate Monica

3

ฉันจะทำ:

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
  }

และละเว้นพารามิเตอร์ period เนื่องจากคุณกำลังพยายามควบคุม periodicy ด้วยตัวเอง


รหัสเดิมของคุณทำงานเร็วที่สุดเนื่องจากคุณยังคงระบุ0สำหรับไฟล์dueTimeพารามิเตอร์ จากTimer.Change:

ถ้า DueTime เป็นศูนย์ (0) วิธีการโทรกลับจะถูกเรียกใช้ทันที


2
จำเป็นต้องทิ้งตัวจับเวลาหรือไม่? ทำไมคุณไม่ใช้Change()วิธีการ?
Alan Coromano

21
การทิ้งตัวจับเวลาทุกครั้งเป็นสิ่งที่ไม่จำเป็นและไม่ถูกต้อง
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)

ถ้าคุณชอบใช้ด้ายวนข้างใน ...

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.
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.