ตัวจับเวลาที่ดีที่สุดสำหรับใช้ในบริการ Windows


108

ฉันต้องการสร้างบริการ windows ซึ่งจะดำเนินการทุกช่วงเวลา N
คำถามคือ
ฉันควรใช้ตัวควบคุมตัวจับเวลาแบบใด: System.Timers.TimerหรือSystem.Threading.Timerอย่างใดอย่างหนึ่ง มีผลต่อบางสิ่งหรือไม่?

ฉันถามเพราะฉันได้ยินหลักฐานมากมายเกี่ยวกับการทำงานที่ไม่ถูกต้องSystem.Timers.Timerในบริการ windows
ขอบคุณ.

คำตอบ:


118

ทั้งสองSystem.Timers.TimerและSystem.Threading.Timerจะทำงานให้บริการ

ตัวจับเวลาที่คุณต้องการหลีกเลี่ยงคือSystem.Web.UI.TimerและSystem.Windows.Forms.Timerซึ่งตามลำดับสำหรับแอปพลิเคชัน ASP และ WinForms การใช้สิ่งเหล่านี้จะทำให้บริการโหลดชุดประกอบเพิ่มเติมซึ่งไม่จำเป็นจริงๆสำหรับประเภทของแอปพลิเคชันที่คุณกำลังสร้าง

ใช้System.Timers.Timerดังตัวอย่างต่อไปนี้ (ตรวจสอบให้แน่ใจว่าคุณใช้ตัวแปรระดับชั้นเรียนเพื่อป้องกันการรวบรวมขยะตามที่ระบุไว้ในคำตอบของ Tim Robinson):

using System;
using System.Timers;

public class Timer1
{
    private static System.Timers.Timer aTimer;

    public static void Main()
    {
        // Normally, the timer is declared at the class level,
        // so that it stays in scope as long as it is needed.
        // If the timer is declared in a long-running method,  
        // KeepAlive must be used to prevent the JIT compiler 
        // from allowing aggressive garbage collection to occur 
        // before the method ends. (See end of method.)
        //System.Timers.Timer aTimer;

        // Create a timer with a ten second interval.
        aTimer = new System.Timers.Timer(10000);

        // Hook up the Elapsed event for the timer.
        aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);

        // Set the Interval to 2 seconds (2000 milliseconds).
        aTimer.Interval = 2000;
        aTimer.Enabled = true;

        Console.WriteLine("Press the Enter key to exit the program.");
        Console.ReadLine();

        // If the timer is declared in a long-running method, use
        // KeepAlive to prevent garbage collection from occurring
        // before the method ends.
        //GC.KeepAlive(aTimer);
    }

    // Specify what you want to happen when the Elapsed event is 
    // raised.
    private static void OnTimedEvent(object source, ElapsedEventArgs e)
    {
        Console.WriteLine("The Elapsed event was raised at {0}", e.SignalTime);
    }
}

/* This code example produces output similar to the following:

Press the Enter key to exit the program.
The Elapsed event was raised at 5/20/2007 8:42:27 PM
The Elapsed event was raised at 5/20/2007 8:42:29 PM
The Elapsed event was raised at 5/20/2007 8:42:31 PM
...
 */

หากคุณเลือกSystem.Threading.Timerคุณสามารถใช้ดังนี้:

using System;
using System.Threading;

class TimerExample
{
    static void Main()
    {
        AutoResetEvent autoEvent     = new AutoResetEvent(false);
        StatusChecker  statusChecker = new StatusChecker(10);

        // Create the delegate that invokes methods for the timer.
        TimerCallback timerDelegate = 
            new TimerCallback(statusChecker.CheckStatus);

        // Create a timer that signals the delegate to invoke 
        // CheckStatus after one second, and every 1/4 second 
        // thereafter.
        Console.WriteLine("{0} Creating timer.\n", 
            DateTime.Now.ToString("h:mm:ss.fff"));
        Timer stateTimer = 
                new Timer(timerDelegate, autoEvent, 1000, 250);

        // When autoEvent signals, change the period to every 
        // 1/2 second.
        autoEvent.WaitOne(5000, false);
        stateTimer.Change(0, 500);
        Console.WriteLine("\nChanging period.\n");

        // When autoEvent signals the second time, dispose of 
        // the timer.
        autoEvent.WaitOne(5000, false);
        stateTimer.Dispose();
        Console.WriteLine("\nDestroying timer.");
    }
}

class StatusChecker
{
    int invokeCount, maxCount;

    public StatusChecker(int count)
    {
        invokeCount  = 0;
        maxCount = count;
    }

    // This method is called by the timer delegate.
    public void CheckStatus(Object stateInfo)
    {
        AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
        Console.WriteLine("{0} Checking status {1,2}.", 
            DateTime.Now.ToString("h:mm:ss.fff"), 
            (++invokeCount).ToString());

        if(invokeCount == maxCount)
        {
            // Reset the counter and signal Main.
            invokeCount  = 0;
            autoEvent.Set();
        }
    }
}

ทั้งสองตัวอย่างมาจากหน้า MSDN


1
ทำไมคุณถึงแนะนำ: GC.KeepAlive (aTimer); aTimer เป็นตัวแปรอินสแตนซ์ที่ถูกต้องดังนั้นตัวอย่างเช่นหากเป็นตัวแปรของรูปแบบอินสแตนซ์จะมีการอ้างอิงเสมอตราบเท่าที่มีรูปแบบไม่ใช่หรือ
giorgim

37

อย่าใช้บริการนี้ สร้างแอปพลิเคชันปกติและสร้างงานตามกำหนดเวลาเพื่อรัน

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

"หากคุณกำลังเขียน Windows Service ที่เรียกใช้ตัวจับเวลาคุณควรประเมินโซลูชันของคุณอีกครั้ง"

- จอนกัลโลเวย์ผู้จัดการโปรแกรมชุมชน ASP.NET MVC ผู้เขียนซูเปอร์ฮีโร่นอกเวลา


26
หากบริการของคุณมีจุดมุ่งหมายให้ทำงานตลอดทั้งวันบริการอาจมีความหมายมากกว่างานตามกำหนดการ หรือการมีบริการอาจทำให้ผู้ดูแลระบบง่ายขึ้นและการบันทึกสำหรับกลุ่มโครงสร้างพื้นฐานที่ไม่เข้าใจเท่าทีมแอปพลิเคชัน อย่างไรก็ตามการตั้งคำถามกับสมมติฐานที่ว่าต้องใช้บริการนั้นถูกต้องทั้งหมดและการให้คะแนนเชิงลบเหล่านี้จะไม่ได้รับการสงวนไว้ +1 ทั้งคู่
sfuqua

2
@mr ไร้สาระ. งานตามกำหนดการเป็นส่วนหลักของระบบปฏิบัติการ โปรดเก็บ FUD ของคุณไว้กับตัวเอง

4
@MR: ฉันไม่ใช่ผู้เผยแพร่ศาสนาฉันเป็นนักสัจนิยม และความเป็นจริงได้สอนฉันว่างานตามกำหนดเวลาไม่ใช่ "รถติดมาก" อันที่จริงแล้วขึ้นอยู่กับคุณในฐานะผู้อ้างว่าสนับสนุนสิ่งนั้น มิฉะนั้นสิ่งที่คุณทำคือการแพร่กระจายความกลัวความไม่แน่นอนและความสงสัย

13
ฉันเกลียดการลุยโคลนที่นี่ แต่ฉันต้องปกป้อง MR สักหน่อย ฉันมีแอปพลิเคชันที่สำคัญหลายตัวที่ทำงานใน บริษัท ของฉันซึ่งเป็นแอปคอนโซลของ Windows ฉันใช้ Windows Task Scheduler เพื่อเรียกใช้งานทั้งหมด อย่างน้อย 5 ครั้งในตอนนี้เราประสบปัญหาที่บริการจัดกำหนดการ "สับสน" อย่างใด งานไม่ดำเนินการและบางส่วนอยู่ในสถานะแปลก ๆ ทางออกเดียวคือการรีบูตเซิร์ฟเวอร์หรือหยุดและเริ่มบริการตัวกำหนดเวลา ไม่ใช่สิ่งที่ฉันทำได้หากไม่มีสิทธิ์ของผู้ดูแลระบบและไม่เป็นที่ยอมรับในสภาพแวดล้อมการผลิต แค่ 0.02 เหรียญของฉัน
SpaceCowboy74

6
มันเกิดขึ้นกับฉันในเซิร์ฟเวอร์สองสามเครื่อง (จนถึงตอนนี้ 3 เครื่อง) จะไม่บอกว่ามันเป็นเรื่องปกติ เพียงแค่พูดบางครั้งการใช้วิธีการของคุณเองในการทำบางสิ่งก็ไม่เลว
SpaceCowboy74

7

อย่างใดอย่างหนึ่งควรทำงานได้ดี ในความเป็นจริง System.Threading.Timer ใช้ System.Timers.Timer ภายใน

ต้องบอกว่าง่ายต่อการใช้ System.Timers.Timer ในทางที่ผิด หากคุณไม่ได้เก็บออบเจ็กต์ Timer ไว้ในตัวแปรที่ใดที่หนึ่งแสดงว่าอาจถูกเก็บรวบรวมขยะ หากเป็นเช่นนั้นตัวจับเวลาของคุณจะไม่ทำงานอีกต่อไป เรียกใช้เมธอด Dispose เพื่อหยุดตัวจับเวลาหรือใช้คลาส System.Threading.Timer ซึ่งเป็นกระดาษห่อหุ้มที่ดีกว่าเล็กน้อย

ตอนนี้คุณพบปัญหาอะไรบ้าง?


ทำให้ฉันสงสัยว่าทำไมแอพ Windows Phone ถึงเข้าถึงได้เฉพาะ System.Threading.Timer
Stonetip

โทรศัพท์ windows อาจมีเฟรมเวิร์กเวอร์ชันที่เบากว่าการมีโค้ดพิเศษทั้งหมดนั้นเพื่อให้สามารถใช้ทั้งสองวิธีได้นั้นไม่จำเป็นจึงไม่รวมอยู่ด้วย ฉันคิดว่าคำตอบของ Nick ให้เหตุผลที่ดีกว่าว่าทำไมโทรศัพท์ Windows ถึงไม่สามารถเข้าถึงได้System.Timers.Timerเพราะมันจะไม่จัดการกับข้อยกเว้นที่เกิดขึ้น
มาลาคี

2

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

นี่จะ:

  • ลดรหัสท่อประปาที่จำลองพฤติกรรมของตัวกำหนดตารางเวลา
  • ให้ความยืดหยุ่นมากขึ้นในแง่ของพฤติกรรมการจัดตารางเวลา (เช่นทำงานเฉพาะวันหยุดสุดสัปดาห์) ด้วยตรรกะการตั้งเวลาทั้งหมดที่แยกออกจากรหัสแอปพลิเคชัน
  • ใช้อาร์กิวเมนต์บรรทัดคำสั่งสำหรับพารามิเตอร์โดยไม่ต้องตั้งค่าคอนฟิกูเรชันในไฟล์ config เป็นต้น
  • ง่ายกว่ามากในการดีบัก / ทดสอบระหว่างการพัฒนา
  • อนุญาตให้ผู้ใช้ฝ่ายสนับสนุนดำเนินการโดยเรียกใช้แอปพลิเคชันคอนโซลโดยตรง (เช่นมีประโยชน์ในระหว่างสถานการณ์สนับสนุน)

3
แต่สิ่งนี้ต้องการผู้ใช้ที่เข้าสู่ระบบ? ดังนั้นบริการอาจดีกว่าถ้าจะทำงานตลอด 24 ชั่วโมงทุกวันบนเซิร์ฟเวอร์
JP Hellemons

1

ตามที่ระบุไว้แล้วทั้งสองSystem.Threading.TimerและSystem.Timers.Timerจะทำงาน ความแตกต่างที่ยิ่งใหญ่ระหว่างสองสิ่งSystem.Threading.Timerนี้คือกระดาษห่อหุ้มอีกอันหนึ่ง

System.Threading.Timerจะมีการจัดการข้อยกเว้นมากขึ้นในขณะที่ System.Timers.Timerจะกลืนข้อยกเว้นทั้งหมด

สิ่งนี้ทำให้ฉันมีปัญหาใหญ่ในอดีตดังนั้นฉันมักจะใช้ 'System.Threading.Timer' และยังคงจัดการข้อยกเว้นของคุณได้เป็นอย่างดี


0

ฉันรู้ว่าเธรดนี้เก่าไปหน่อย แต่มันมีประโยชน์สำหรับสถานการณ์เฉพาะที่ฉันมีและฉันคิดว่ามันคุ้มค่าที่จะทราบว่ามีเหตุผลอื่นที่System.Threading.Timerอาจเป็นแนวทางที่ดี เมื่อคุณต้องดำเนินงานเป็นระยะ ๆ ซึ่งอาจใช้เวลานานและคุณต้องการให้แน่ใจว่ามีการใช้ระยะเวลารอทั้งหมดระหว่างงานหรือหากคุณไม่ต้องการให้งานทำงานอีกครั้งก่อนที่งานก่อนหน้าจะเสร็จสิ้นในกรณีที่ งานใช้เวลานานกว่าช่วงเวลาจับเวลา คุณสามารถใช้สิ่งต่อไปนี้:

using System;
using System.ServiceProcess;
using System.Threading;

    public partial class TimerExampleService : ServiceBase
    {
        private AutoResetEvent AutoEventInstance { get; set; }
        private StatusChecker StatusCheckerInstance { get; set; }
        private Timer StateTimer { get; set; }
        public int TimerInterval { get; set; }

        public CaseIndexingService()
        {
            InitializeComponent();
            TimerInterval = 300000;
        }

        protected override void OnStart(string[] args)
        {
            AutoEventInstance = new AutoResetEvent(false);
            StatusCheckerInstance = new StatusChecker();

            // Create the delegate that invokes methods for the timer.
            TimerCallback timerDelegate =
                new TimerCallback(StatusCheckerInstance.CheckStatus);

            // Create a timer that signals the delegate to invoke 
            // 1.CheckStatus immediately, 
            // 2.Wait until the job is finished,
            // 3.then wait 5 minutes before executing again. 
            // 4.Repeat from point 2.
            Console.WriteLine("{0} Creating timer.\n",
                DateTime.Now.ToString("h:mm:ss.fff"));
            //Start Immediately but don't run again.
            StateTimer = new Timer(timerDelegate, AutoEventInstance, 0, Timeout.Infinite);
            while (StateTimer != null)
            {
                //Wait until the job is done
                AutoEventInstance.WaitOne();
                //Wait for 5 minutes before starting the job again.
                StateTimer.Change(TimerInterval, Timeout.Infinite);
            }
            //If the Job somehow takes longer than 5 minutes to complete then it wont matter because we will always wait another 5 minutes before running again.
        }

        protected override void OnStop()
        {
            StateTimer.Dispose();
        }
    }

    class StatusChecker
        {

            public StatusChecker()
            {
            }

            // This method is called by the timer delegate.
            public void CheckStatus(Object stateInfo)
            {
                AutoResetEvent autoEvent = (AutoResetEvent)stateInfo;
                Console.WriteLine("{0} Start Checking status.",
                    DateTime.Now.ToString("h:mm:ss.fff"));
                //This job takes time to run. For example purposes, I put a delay in here.
                int milliseconds = 5000;
                Thread.Sleep(milliseconds);
                //Job is now done running and the timer can now be reset to wait for the next interval
                Console.WriteLine("{0} Done Checking status.",
                    DateTime.Now.ToString("h:mm:ss.fff"));
                autoEvent.Set();
            }
        }
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.