ฉันจะกำหนดเวลาให้บริการ C # Windows ทำงานทุกวันได้อย่างไร


84

ฉันมีบริการที่เขียนด้วยภาษา C # (.NET 1.1) และต้องการให้ดำเนินการล้างข้อมูลตอนเที่ยงคืนของทุกคืน ฉันต้องเก็บรหัสทั้งหมดไว้ในบริการดังนั้นวิธีที่ง่ายที่สุดในการทำสิ่งนี้คืออะไร? ใช้Thread.Sleep()และตรวจสอบเวลาที่หมุนไปหรือไม่?


10
คุณต้องการสร้างกระบวนการ. NET ที่อยู่ในหน่วยความจำทั้งวันและทำอะไรบางอย่างในเวลาเที่ยงคืนหรือไม่? เหตุใดคุณจึงไม่สามารถตั้งค่าเหตุการณ์ของระบบที่เริ่มทำงานในเวลาเที่ยงคืน (หรือเวลาที่กำหนดได้อื่น ๆ ) ที่เริ่มต้นแอปของคุณทำสิ่งนั้นแล้วจากไปเมื่อติดตั้งเครื่องมือของคุณไม่ได้
Robert P

6
ฉันรู้ว่าฉันจะโกรธนิดหน่อยถ้าฉันพบว่าฉันมีบริการที่มีการจัดการที่ใช้หน่วยความจำ 20-40 เมกะไบต์ทำงานตลอดเวลาซึ่งไม่ได้ทำอะไรเลยนอกจากวันละครั้ง :)
Robert P

คำตอบ:


86

ฉันจะไม่ใช้ Thread.Sleep () ใช้งานที่กำหนดเวลาไว้ (ตามที่คนอื่นพูดถึง) หรือตั้งค่าตัวจับเวลาภายในบริการของคุณซึ่งจะเริ่มทำงานเป็นระยะ ๆ (เช่นทุกๆ 10 นาที) และตรวจสอบว่าวันที่เปลี่ยนแปลงไปหรือไม่ตั้งแต่การรันครั้งล่าสุด:

private Timer _timer;
private DateTime _lastRun = DateTime.Now.AddDays(-1);

protected override void OnStart(string[] args)
{
    _timer = new Timer(10 * 60 * 1000); // every 10 minutes
    _timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
    _timer.Start();
    //...
}


private void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
    // ignore the time, just compare the date
    if (_lastRun.Date < DateTime.Now.Date)
    {
        // stop the timer while we are running the cleanup task
        _timer.Stop();
        //
        // do cleanup stuff
        //
        _lastRun = DateTime.Now;
        _timer.Start();
    }
}

28
มันจะสมเหตุสมผลกว่าไหมที่จะตั้งเวลาให้หมดเวลาเที่ยงคืนแทนที่จะตื่นขึ้นมาทุกนาทีเพื่อตรวจสอบเวลา
Kibbee

11
@Kibbee: บางทีถ้าบริการไม่ทำงานตอนเที่ยงคืน (ไม่ว่าด้วยเหตุผลใดก็ตาม) คุณอาจต้องการดำเนินการทันทีที่บริการทำงานอีกครั้ง
M4N

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

3
@Kibbee: ใช่ฉันเห็นด้วย (เป็นรายละเอียดเล็กน้อยที่เรากำลังคุยกัน) แต่แม้ว่าตัวจับเวลาจะเริ่มทำงานทุกๆ 10 วินาที แต่ก็ไม่ได้ทำอันตรายใด ๆ
M4N

1
ทางออกที่ดี แต่โค้ดด้านบนหายไป _timer.start () และ _lastRun ควรตั้งค่าเป็นเมื่อวานนี้: DateTime ส่วนตัว _lastRun = DateTime.Now.AddDays (-1);
Zulu Z

72

ตรวจสอบQuartz.NET คุณสามารถใช้ได้ภายในบริการของ Windows ช่วยให้คุณสามารถเรียกใช้งานตามตารางเวลาที่กำหนดไว้และยังรองรับไวยากรณ์ "cron job" แบบง่ายๆ ฉันประสบความสำเร็จมากมายกับมัน

นี่คือตัวอย่างการใช้งานโดยย่อ:

// Instantiate the Quartz.NET scheduler
var schedulerFactory = new StdSchedulerFactory();
var scheduler = schedulerFactory.GetScheduler();

// Instantiate the JobDetail object passing in the type of your
// custom job class. Your class merely needs to implement a simple
// interface with a single method called "Execute".
var job = new JobDetail("job1", "group1", typeof(MyJobClass));

// Instantiate a trigger using the basic cron syntax.
// This tells it to run at 1AM every Monday - Friday.
var trigger = new CronTrigger(
    "trigger1", "group1", "job1", "group1", "0 0 1 ? * MON-FRI");

// Add the job to the scheduler
scheduler.AddJob(job, true);
scheduler.ScheduleJob(trigger);

2
คำแนะนำที่ดี - ทันทีที่คุณทำบางสิ่งที่ซับซ้อนกว่าตัวจับเวลาพื้นฐานเล็กน้อยคุณควรดูที่ Quartz
serg10

2
ควอตซ์ใช้งานง่ายมากฉันขอยืนยันว่าแม้จะเป็นงานที่เรียบง่ายสุด ๆ เพียงแค่ใช้มันต่อไป จะช่วยให้สามารถปรับเปลี่ยนการตั้งเวลาได้อย่างง่ายดาย
Andy White

ง่ายสุดคือไม่จริงเหรอ? มือใหม่ส่วนใหญ่จะเจอปัญหานี้ที่นี่stackoverflow.com/questions/24628372/…
Emil

21

งานประจำวัน? ดูเหมือนว่ามันควรจะเป็นงานตามกำหนดเวลา (แผงควบคุม) - ไม่จำเป็นต้องใช้บริการที่นี่


15

ต้องเป็นบริการจริงหรือไม่? คุณสามารถใช้งานที่กำหนดเวลาในตัวในแผงควบคุมของ windows ได้ไหม


เนื่องจากมีบริการอยู่แล้วจึงอาจเพิ่มฟังก์ชันการทำงานที่นั่นได้ง่ายขึ้น
M4N

1
การแปลงบริการวัตถุประสงค์ที่แคบเช่นนี้เป็นแอปคอนโซลควรเป็นเรื่องเล็กน้อย
Stephen Martin

7
เขาบอกแล้วว่า "ฉันต้องเก็บรหัสทั้งหมดที่มีอยู่ในบริการ" ฉันไม่รู้สึกว่าจำเป็นต้องตั้งคำถามกับข้อกำหนดเดิม ขึ้นอยู่กับ แต่บางครั้งก็มีเหตุผลที่ดีมากที่จะใช้บริการในงานที่กำหนดไว้
jeremcc

3
+1: เพราะคุณทุกคนต้องมองปัญหาของคุณจากทุกด้าน
GvS

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

3

วิธีที่ฉันทำได้คือการจับเวลา

เรียกใช้ตัวจับเวลาเซิร์ฟเวอร์ให้ตรวจสอบชั่วโมง / นาทีทุก 60 วินาที

หากเป็นชั่วโมง / นาทีที่ถูกต้องให้เรียกใช้กระบวนการของคุณ

จริงๆแล้วฉันได้สรุปสิ่งนี้ออกเป็นคลาสพื้นฐานที่ฉันเรียกว่า OnceADayRunner

ขอทำความสะอาดโค้ดหน่อยแล้วจะโพสต์ไว้ที่นี่

    private void OnceADayRunnerTimer_Elapsed(object sender, ElapsedEventArgs e)
    {
        using (NDC.Push(GetType().Name))
        {
            try
            {
                log.DebugFormat("Checking if it's time to process at: {0}", e.SignalTime);
                log.DebugFormat("IsTestMode: {0}", IsTestMode);

                if ((e.SignalTime.Minute == MinuteToCheck && e.SignalTime.Hour == HourToCheck) || IsTestMode)
                {
                    log.InfoFormat("Processing at: Hour = {0} - Minute = {1}", e.SignalTime.Hour, e.SignalTime.Minute);
                    OnceADayTimer.Enabled = false;
                    OnceADayMethod();
                    OnceADayTimer.Enabled = true;

                    IsTestMode = false;
                }
                else
                {
                    log.DebugFormat("Not correct time at: Hour = {0} - Minute = {1}", e.SignalTime.Hour, e.SignalTime.Minute);
                }
            }
            catch (Exception ex)
            {
                OnceADayTimer.Enabled = true;
                log.Error(ex.ToString());
            }

            OnceADayTimer.Start();
        }
    }

เนื้อของวิธีนี้อยู่ในเช็ค e.SignalTime.Minute / Hour

มีตะขออยู่ในนั้นสำหรับการทดสอบ ฯลฯ แต่นี่คือสิ่งที่ตัวจับเวลาที่ผ่านไปของคุณอาจดูเหมือนเพื่อให้ใช้งานได้ทั้งหมด


ความล่าช้าเล็กน้อยเนื่องจากเซิร์ฟเวอร์ไม่ว่างอาจทำให้พลาดชั่วโมง + นาที
Jon B

1
จริง. เมื่อฉันทำสิ่งนี้ฉันตรวจสอบ: เวลานี้มากกว่า 00:00 หรือไม่? ถ้าเป็นเช่นนั้นเป็นเวลานานกว่า 24 ชั่วโมงแล้วที่ฉันทำงานครั้งสุดท้ายหรือไม่? ถ้าเป็นเช่นนั้นให้เรียกใช้งานมิฉะนั้นให้รออีกครั้ง
ZombieSheep

2

ตามที่คนอื่นเขียนไปแล้วตัวจับเวลาเป็นตัวเลือกที่ดีที่สุดในสถานการณ์ที่คุณอธิบาย

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

หากเหตุผลที่คุณต้องการดำเนินการในเวลาเที่ยงคืนคือคุณคาดว่าปริมาณงานบนคอมพิวเตอร์ของคุณจะเหลือน้อยควรดูแลให้ดีขึ้น: ผู้อื่นมักจะตั้งสมมติฐานเดียวกันนี้และทันใดนั้นคุณก็มีการดำเนินการล้างข้อมูล 100 ครั้งเริ่มตั้งแต่เวลา 00:00 ถึง 0:00 น. : 01 น

ในกรณีนี้คุณควรพิจารณาเริ่มการล้างข้อมูลในเวลาอื่น ฉันมักจะทำสิ่งเหล่านั้นไม่ใช่เวลานาฬิกา แต่เป็นเวลาครึ่งชั่วโมง (1.30 น. เป็นความชอบส่วนตัวของฉัน)


1

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


หากคุณไม่ตรวจสอบเพื่อให้แน่ใจว่ามันยังไม่ได้ทำงานสักครั้งมีโอกาสที่คุณจะกด 00:00 สองครั้งได้หากคุณตรวจสอบ 45 วินาที
Michael Meadows

จุดดี. ปัญหานี้สามารถหลีกเลี่ยงได้โดยโทรไปที่ Sleep (15000) ในตอนท้ายของขั้นตอนการตรวจสอบ (หรืออาจถึง 16000 ms เพื่อให้อยู่ในด้านที่ปลอดภัย ;-)
Treb

ฉันเห็นด้วยคุณควรตรวจสอบว่ามันทำงานไปแล้วหรือไม่ไม่ว่าคุณจะเลือกระยะเวลาใดก็ตาม
GWLlosa


0

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


-2

ลองสิ่งนี้:

public partial class Service : ServiceBase
{
    private Timer timer;
    public Service()
    {
        InitializeComponent();
    }

    protected override void OnStart(string[] args)
    {
        SetTimer();
    }

    private void SetTimer()
    {
        if (timer == null)
        {
            timer = new Timer();
            timer.AutoReset = true;
            timer.Interval = 60000 * Convert.ToDouble(ConfigurationManager.AppSettings["IntervalMinutes"]);
            timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);
            timer.Start();
        }
    }

    private void timer_Elapsed(object source, System.Timers.ElapsedEventArgs e)
    {
        //Do some thing logic here
    }

    protected override void OnStop()
    {
        // disposed all service objects
    }
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.