Bagaimana cara agar aplikasi konsol .NET tetap berjalan?

105

Pertimbangkan aplikasi Konsol yang memulai beberapa layanan di utas terpisah. Yang perlu dilakukan hanyalah menunggu pengguna menekan Ctrl + C untuk mematikannya.

Manakah dari berikut ini yang merupakan cara terbaik untuk melakukan ini?

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
}

Atau ini, menggunakan 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
}
intoOrbit
sumber

Jawaban:

64

Anda selalu ingin mencegah penggunaan while loop, terutama saat Anda memaksa kode untuk memeriksa ulang variabel. Itu membuang-buang sumber daya CPU dan memperlambat program Anda.

Saya pasti akan mengatakan yang pertama.

Mike
sumber
2
+1. Selain itu, karena booltidak dideklarasikan sebagai volatile, ada kemungkinan pasti bahwa pembacaan berikutnya ke _quitFlagdalam whileloop akan dioptimalkan, yang mengarah ke loop tak terbatas.
Adam Robinson
2
Tidak ada cara yang disarankan untuk melakukan ini. Saya mengharapkan ini sebagai jawaban.
Iúri dos Anjos
30

Alternatifnya, solusi yang lebih sederhana hanyalah:

Console.ReadLine();
Cocowalla
sumber
Saya hendak menyarankan itu, tetapi itu tidak akan berhenti hanya pada Ctrl-C
Thomas Levesque
Saya mendapat kesan bahwa CTRL-C hanyalah sebuah contoh - setiap masukan pengguna untuk menutupnya
Cocowalla
Ingatlah bahwa 'Console.ReadLine ()' adalah pemblokiran thread. Jadi aplikasi akan tetap berjalan tetapi tidak melakukan apa pun selain menunggu pengguna memasukkan baris
fabriciorissetto
2
@fabriciorissetto Pertanyaan OP menyatakan 'memulai hal-hal asinkron', sehingga aplikasi akan melakukan pekerjaan pada utas lain
Cocowalla
1
@Cocowalla Saya merindukan itu. Salahku!
fabriciorissetto
12

Anda bisa melakukannya (dan menghapus CancelKeyPressevent handler):

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

Tidak yakin apakah itu lebih baik, tetapi saya tidak suka gagasan memanggil Thread.Sleepdalam satu lingkaran .. Saya pikir itu lebih bersih untuk memblokir masukan pengguna.

Thomas Levesque
sumber
Saya tidak suka Anda memeriksa tombol Ctrl + C, alih-alih sinyal yang dipicu oleh Ctrl + C.
CodesInChaos
9

Saya lebih suka menggunakan Application.Run

static void Main(string[] args) {

   //Do your stuff here

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

   //Cleanup/Before Quit
}

dari dokumen:

Mulai menjalankan loop pesan aplikasi standar di thread saat ini, tanpa formulir.

ygaradon
sumber
10
Tapi kemudian Anda mengambil ketergantungan pada formulir windows hanya untuk ini. Tidak terlalu banyak masalah dengan kerangka .NET tradisional, tetapi tren saat ini mengarah pada penerapan modular termasuk hanya bagian yang Anda butuhkan.
CodesInChaos
4

Sepertinya Anda membuatnya lebih sulit dari yang Anda butuhkan. Mengapa tidak hanya Joinutas setelah Anda memberi isyarat untuk berhenti?

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);
        }
    }
}
Ed Power
sumber
2
Dalam kasus saya, saya tidak mengontrol utas tempat hal-hal asinkron berjalan.
intoOrbit
1) Saya tidak suka Anda memeriksa tombol Ctrl + C, alih-alih sinyal yang dipicu oleh Ctrl + C. 2) Pendekatan Anda tidak berfungsi jika aplikasi menggunakan Tugas, bukan thread pekerja tunggal.
CodesInChaos
2

Ini juga memungkinkan untuk memblokir utas / program berdasarkan token pembatalan.

token.WaitHandle.WaitOne();

WaitHandle diberi tanda ketika token dibatalkan.

Saya telah melihat teknik ini digunakan oleh Microsoft.Azure.WebJobs.JobHost, di mana token itu berasal dari sumber token pembatalan WebJobsShutdownWatcher (pengamat file yang mengakhiri pekerjaan).

Ini memberi beberapa kendali atas kapan program bisa berakhir.

CRice
sumber
1
Ini adalah jawaban yang sangat bagus untuk aplikasi Konsol dunia nyata yang perlu mendengarkan CTL+Ckarena menjalankan operasi yang berjalan lama, atau merupakan daemon, yang juga harus mematikan thread pekerja dengan baik. Anda akan melakukannya dengan CancelToken dan jawaban ini memanfaatkan WaitHandleyang sudah ada, daripada membuat yang baru.
mdisibio
1

Dari dua yang pertama lebih baik

_quitEvent.WaitOne();

karena di thread kedua thread yang aktif setiap satu milidetik akan dialihkan ke interupsi OS yang mahal

Naveen
sumber
Ini adalah alternatif yang baik untuk Consolemetode jika Anda tidak memiliki konsol yang terpasang (karena, misalnya, program dimulai oleh layanan)
Marco Sulla
0

Anda harus melakukannya seperti yang Anda lakukan jika Anda memprogram layanan windows. Anda tidak akan pernah menggunakan pernyataan while sebagai gantinya Anda akan menggunakan delegasi. WaitOne () umumnya digunakan sambil menunggu utas dibuang - Thread.Sleep () - tidak disarankan - Pernahkah Anda berpikir untuk menggunakan System.Timers.Timer menggunakan acara itu untuk memeriksa acara mematikan?

KenL
sumber