จะทำให้แอปคอนโซล. NET ทำงานได้อย่างไร


105

พิจารณาแอปพลิเคชันคอนโซลที่เริ่มต้นบริการบางอย่างในเธรดแยกต่างหาก สิ่งที่ต้องทำคือรอให้ผู้ใช้กด Ctrl + C เพื่อปิดเครื่อง

วิธีใดเป็นวิธีที่ดีกว่าในการดำเนินการนี้

static ManualResetEvent _quitEvent = new ManualResetEvent(false);

static void Main() {
    Console.CancelKeyPress += (sender, eArgs) => {
        _quitEvent.Set();
        eArgs.Cancel = true;
    };

    // kick off asynchronous stuff 

    _quitEvent.WaitOne();

    // cleanup/shutdown and quit
}

หรือสิ่งนี้โดยใช้ Thread.Sleep (1):

static bool _quitFlag = false;

static void Main() {
    Console.CancelKeyPress += delegate {
        _quitFlag = true;
    };

    // kick off asynchronous stuff 

    while (!_quitFlag) {
        Thread.Sleep(1);
    }

    // cleanup/shutdown and quit
}

คำตอบ:


64

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

ฉันจะพูดคนแรกแน่นอน


2
+1. นอกจากนี้เนื่องจากboolไม่ได้ประกาศเป็นvolatileจึงมีความเป็นไปได้แน่นอนที่การอ่าน_quitFlagในwhileลูปในภายหลังจะได้รับการปรับให้เหมาะสมซึ่งนำไปสู่การวนซ้ำที่ไม่มีที่สิ้นสุด
Adam Robinson

2
ไม่มีวิธีที่แนะนำในการดำเนินการนี้ ฉันคาดหวังว่านี่จะเป็นคำตอบ
Iúri dos Anjos

30

อีกวิธีหนึ่งวิธีแก้ปัญหาที่ง่ายกว่านั้นคือ:

Console.ReadLine();

ฉันกำลังจะแนะนำอย่างนั้น แต่จะไม่หยุดเฉพาะใน Ctrl-C
Thomas Levesque

ฉันรู้สึกว่า CTRL-C เป็นเพียงตัวอย่างเท่านั้น - ข้อมูลใด ๆ ของผู้ใช้เพื่อปิด
Cocowalla

โปรดจำไว้ว่า 'Console.ReadLine ()' คือการบล็อกเธรด ดังนั้นแอปพลิเคชันจะยังคงทำงานอยู่ แต่ไม่ทำอะไรเลยนอกจากรอให้ผู้ใช้ป้อนบรรทัด
fabriciorisset

2
@fabriciorissetto คำถาม OP ระบุว่า 'เริ่มต้นสิ่งที่ไม่ตรงกัน' ดังนั้นแอปพลิเคชันจะทำงานในเธรดอื่น
Cocowalla

1
@Cocowalla ฉันพลาดอย่างนั้น ความผิดฉันเอง!
fabriciorisset ถึง

12

คุณสามารถทำได้ (และลบCancelKeyPressตัวจัดการเหตุการณ์):

while(!_quitFlag)
{
    var keyInfo = Console.ReadKey();
    _quitFlag = keyInfo.Key == ConsoleKey.C
             && keyInfo.Modifiers == ConsoleModifiers.Control;
}

ไม่แน่ใจว่าจะดีกว่าไหม แต่ฉันไม่ชอบแนวคิดเรื่องการโทรThread.Sleepวนซ้ำ .. ฉันคิดว่าการบล็อกอินพุตของผู้ใช้จะดีกว่า


ฉันไม่ชอบที่คุณกำลังตรวจสอบคีย์ Ctrl + C แทนที่จะเป็นสัญญาณที่เรียกโดย Ctrl + C
CodesInChaos

9

ฉันชอบใช้แอปพลิเคชัน Run

static void Main(string[] args) {

   //Do your stuff here

   System.Windows.Forms.Application.Run();

   //Cleanup/Before Quit
}

จากเอกสาร:

เริ่มการรันลูปข้อความแอปพลิเคชันมาตรฐานบนเธรดปัจจุบันโดยไม่มีแบบฟอร์ม


10
แต่จากนั้นคุณจะพึ่งพารูปแบบ windows เพื่อสิ่งนี้ ไม่ใช่ปัญหากับ. NET framework แบบดั้งเดิมมากเกินไป แต่แนวโน้มในปัจจุบันมุ่งไปสู่การปรับใช้แบบโมดูลรวมถึงเฉพาะส่วนที่คุณต้องการ
CodesInChaos

4

ดูเหมือนว่าคุณกำลังทำมันยากกว่าที่คุณต้องการ ทำไมไม่เพียงแค่Joinเธรดหลังจากที่คุณส่งสัญญาณให้หยุด?

class Program
{
    static void Main(string[] args)
    {
        Worker worker = new Worker();
        Thread t = new Thread(worker.DoWork);
        t.IsBackground = true;
        t.Start();

        while (true)
        {
            var keyInfo = Console.ReadKey();
            if (keyInfo.Key == ConsoleKey.C && keyInfo.Modifiers == ConsoleModifiers.Control)
            {
                worker.KeepGoing = false;
                break;
            }
        }
        t.Join();
    }
}

class Worker
{
    public bool KeepGoing { get; set; }

    public Worker()
    {
        KeepGoing = true;
    }

    public void DoWork()
    {
        while (KeepGoing)
        {
            Console.WriteLine("Ding");
            Thread.Sleep(200);
        }
    }
}

2
ในกรณีของฉันฉันไม่ได้ควบคุมเธรดที่สิ่งที่อะซิงโครนัสทำงานอยู่
intoOrbit

1) ฉันไม่ชอบที่คุณกำลังตรวจสอบคีย์ Ctrl + C แทนที่จะเป็นสัญญาณที่เรียกโดย Ctrl + C 2) แนวทางของคุณไม่ได้ผลหากแอปพลิเคชันใช้ Tasks แทนเธรดผู้ปฏิบัติงานเดียว
CodesInChaos

2

นอกจากนี้ยังสามารถบล็อกเธรด / โปรแกรมโดยใช้โทเค็นการยกเลิก

token.WaitHandle.WaitOne();

WaitHandle ถูกส่งสัญญาณเมื่อโทเค็นถูกยกเลิก

ฉันเคยเห็นเทคนิคนี้ใช้โดย Microsoft.Azure.WebJobs.JobHost โดยที่โทเค็นมาจากแหล่งโทเค็นการยกเลิกของ WebJobsShutdownWatcher (ผู้เฝ้าดูไฟล์ที่จบงาน)

สิ่งนี้ช่วยให้สามารถควบคุมเวลาที่โปรแกรมสามารถสิ้นสุดได้


1
นี่เป็นคำตอบที่ยอดเยี่ยมสำหรับแอปคอนโซลในโลกแห่งความเป็นจริงที่ต้องฟังCTL+Cเนื่องจากแอปทำงานเป็นเวลานานหรือเป็นดีมอนซึ่งควรปิดเธรดผู้ปฏิบัติงานอย่างสง่างาม คุณจะทำเช่นนั้นด้วย CancelToken ดังนั้นคำตอบนี้จึงใช้ประโยชน์จากสิ่งWaitHandleที่มีอยู่แล้วแทนที่จะสร้างใหม่
mdisibio

1

จากสองคนแรกจะดีกว่า

_quitEvent.WaitOne();

เนื่องจากในครั้งที่สองเธรดจะตื่นขึ้นมาทุก ๆ หนึ่งมิลลิวินาทีจะถูกส่งไปยังการขัดจังหวะของระบบปฏิบัติการซึ่งมีราคาแพง


นี่เป็นทางเลือกที่ดีสำหรับConsoleวิธีการหากคุณไม่ได้ติดคอนโซลไว้ (เพราะเช่นโปรแกรมเริ่มต้นด้วยบริการ)
Marco Sulla

0

คุณควรทำเช่นเดียวกับที่คุณทำหากคุณกำลังเขียนโปรแกรมบริการ windows คุณจะไม่ใช้คำสั่ง while แทนคุณจะใช้ผู้รับมอบสิทธิ์ โดยทั่วไปจะใช้ WaitOne () ในขณะที่รอให้เธรดทิ้ง - ไม่แนะนำให้ใช้ Thread.Sleep () - คุณเคยคิดที่จะใช้ System.Timers.Timer โดยใช้เหตุการณ์นั้นเพื่อตรวจสอบเหตุการณ์ปิดหรือไม่

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