Làm cách nào để giữ cho ứng dụng bảng điều khiển .NET tiếp tục chạy?


104

Hãy xem xét một ứng dụng Console khởi động một số dịch vụ trong một chuỗi riêng biệt. Tất cả những gì cần làm là đợi người dùng nhấn Ctrl + C để tắt nó.

Cách nào sau đây là cách tốt hơn để làm điều này?

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
}

Hoặc điều này, sử dụng 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
}

Câu trả lời:


63

bạn luôn muốn ngăn việc sử dụng vòng lặp while, đặc biệt khi bạn buộc mã kiểm tra lại các biến. Nó gây lãng phí tài nguyên CPU và làm chậm chương trình của bạn.

Tôi chắc chắn sẽ nói điều đầu tiên.


2
+1. Ngoài ra, vì boolkhông được khai báo là volatile, nên có khả năng chắc chắn rằng các lần đọc tiếp theo _quitFlagtrong whilevòng lặp sẽ bị tối ưu hóa, dẫn đến một vòng lặp vô hạn.
Adam Robinson

2
Thiếu cách được đề xuất để làm điều này. Tôi đã mong đợi điều này như một câu trả lời.
Iúri dos Anjos

30

Ngoài ra, một giải pháp đơn giản hơn chỉ là:

Console.ReadLine();

Tôi đã định đề xuất điều đó, nhưng nó sẽ không chỉ dừng lại trên Ctrl-C
Thomas Levesque

Tôi có ấn tượng rằng CTRL-C chỉ là một ví dụ - bất kỳ đầu vào nào của người dùng để đóng nó
Cocowalla

Hãy nhớ rằng 'Console.ReadLine ()' đang chặn luồng. Vì vậy, các ứng dụng sẽ vẫn được hoạt động nhưng không làm gì hơn là chờ đợi người dùng nhập vào một dòng
fabriciorissetto

2
@fabriciorissetto 'đá ra khỏi những thứ không đồng bộ' Các trạng thái OP câu hỏi, vì vậy ứng dụng sẽ được thực hiện công việc trên thread khác
Cocowalla

1
@Cocowalla Tôi đã bỏ lỡ điều đó. Lỗi của tôi!
fabriciorissetto

12

Bạn có thể làm điều đó (và xóa CancelKeyPresstrình xử lý sự kiện):

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

Không chắc liệu điều đó có tốt hơn không, nhưng tôi không thích ý tưởng gọi Thread.Sleeptheo vòng lặp .. Tôi nghĩ rằng việc chặn trên đầu vào của người dùng sẽ rõ ràng hơn.


Tôi không thích việc bạn đang kiểm tra các phím Ctrl + C, thay vì tín hiệu được kích hoạt bởi Ctrl + C.
CodesInChaos

9

Tôi thích sử dụng Ứng dụng hơn.

static void Main(string[] args) {

   //Do your stuff here

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

   //Cleanup/Before Quit
}

từ các tài liệu:

Bắt đầu chạy vòng lặp thông báo ứng dụng tiêu chuẩn trên luồng hiện tại, không có biểu mẫu.


9
Nhưng sau đó bạn phụ thuộc vào các biểu mẫu cửa sổ chỉ cho điều này. Không có quá nhiều vấn đề với .NET framework truyền thống, nhưng xu hướng hiện tại là triển khai theo mô-đun chỉ bao gồm những phần bạn cần.
CodesInChaos

4

Có vẻ như bạn đang làm khó hơn mức cần thiết. Tại sao không chỉ Joinchuỗi sau khi bạn đã ra hiệu dừng lại?

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
Trong trường hợp của tôi, tôi không kiểm soát các luồng mà nội dung không đồng bộ chạy trên đó.
intoOrbit

1) Tôi không thích việc bạn đang kiểm tra các phím Ctrl + C, thay vì tín hiệu được kích hoạt bởi Ctrl + C. 2) Cách tiếp cận của bạn không hoạt động nếu ứng dụng sử dụng Tác vụ thay vì một luồng nhân viên.
CodesInChaos

2

Cũng có thể chặn chuỗi / chương trình dựa trên mã thông báo hủy.

token.WaitHandle.WaitOne();

WaitHandle được báo hiệu khi mã thông báo bị hủy.

Tôi đã thấy kỹ thuật này được sử dụng bởi Microsoft.Azure.WebJobs.JobHost, nơi mã thông báo đến từ nguồn mã thông báo hủy của WebJobsShutdownWatcher (một trình xem tệp kết thúc công việc).

Điều này cung cấp một số quyền kiểm soát khi chương trình có thể kết thúc.


1
Đây là một câu trả lời tuyệt vời cho bất kỳ ứng dụng Console nào trong thế giới thực cần lắng nghe CTL+Cvì nó đang thực hiện một hoạt động lâu dài hoặc là một daemon, cũng sẽ tắt các luồng công nhân của nó một cách duyên dáng. Bạn sẽ làm điều đó với một CancelToken và vì vậy câu trả lời này sẽ tận dụng một câu trả lời WaitHandleđã tồn tại, thay vì tạo một câu trả lời mới.
mdisibio

1

Trong số hai cái đầu tiên tốt hơn

_quitEvent.WaitOne();

bởi vì trong cái thứ hai, luồng thức dậy cứ sau một mili giây sẽ bị chuyển sang ngắt hệ điều hành, điều này rất tốn kém


Đây là một giải pháp thay thế tốt cho Consolecác phương pháp nếu bạn không có bảng điều khiển đi kèm (ví dụ: vì chương trình được khởi động bởi một dịch vụ)
Marco Sulla

0

Bạn nên làm điều đó giống như bạn làm nếu bạn đang lập trình một dịch vụ windows. Bạn sẽ không bao giờ sử dụng câu lệnh while thay vào đó bạn sẽ sử dụng một đại biểu. WaitOne () thường được sử dụng trong khi chờ các luồng xử lý - Thread.Sleep () - không được tư vấn - Bạn đã nghĩ đến việc sử dụng System.Timers.Timer sử dụng sự kiện đó để kiểm tra sự kiện tắt chưa?

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.