Comment maintenir une application console .NET en cours d'exécution?

105

Considérez une application console qui démarre certains services dans un thread distinct. Tout ce qu'il faut faire est d'attendre que l'utilisateur appuie sur Ctrl + C pour l'arrêter.

Laquelle des propositions suivantes est la meilleure façon de procéder?

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
}

Ou ceci, en utilisant 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
la source

Réponses:

64

vous voulez toujours éviter d'utiliser des boucles while, en particulier lorsque vous forcez le code à revérifier les variables. Cela gaspille les ressources du processeur et ralentit votre programme.

Je dirais certainement le premier.

Mike
la source
2
+1. De plus, puisque le booln'est pas déclaré comme volatile, il y a la possibilité définitive que les lectures ultérieures _quitFlagdans la whileboucle soient optimisées, conduisant à une boucle infinie.
Adam Robinson
2
Manque la manière recommandée de faire ceci. J'attendais cela comme une réponse.
Iúri dos Anjos
30

Alternativement, une solution plus simple est simplement:

Console.ReadLine();
Cocowalla
la source
J'étais sur le point de suggérer cela, mais cela ne s'arrêtera pas uniquement sur Ctrl-C
Thomas Levesque
J'ai eu l'impression que CTRL-C n'était qu'un exemple - n'importe quelle entrée d'utilisateur pour le fermer
Cocowalla
N'oubliez pas que «Console.ReadLine ()» bloque les threads. Ainsi, l'application serait toujours en cours d'exécution mais ne ferait rien d'autre que d'attendre que l'utilisateur entre une ligne
fabriciorissetto
2
@fabriciorissetto La question OP indique «lancer des trucs asynchrones», donc l'application effectuera un travail sur un autre thread
Cocowalla
1
@Cocowalla J'ai raté ça. Ma faute!
fabriciorissetto
12

Vous pouvez le faire (et supprimer le CancelKeyPressgestionnaire d'événements):

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

Je ne sais pas si c'est mieux, mais je n'aime pas l'idée d'appeler Thread.Sleepen boucle .. Je pense que c'est plus propre de bloquer les entrées de l'utilisateur.

Thomas Levesque
la source
Je n'aime pas que vous vérifiiez les touches Ctrl + C, au lieu du signal déclenché par Ctrl + C.
CodesInChaos
9

Je préfère utiliser l' application.

static void Main(string[] args) {

   //Do your stuff here

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

   //Cleanup/Before Quit
}

à partir de la documentation:

Commence à exécuter une boucle de message d'application standard sur le thread actuel, sans formulaire.

Ygaradon
la source
10
Mais alors vous prenez une dépendance sur les formulaires Windows juste pour cela. Pas trop de problème avec le framework .NET traditionnel, mais la tendance actuelle est aux déploiements modulaires comprenant uniquement les pièces dont vous avez besoin.
CodesInChaos
4

On dirait que vous rendez les choses plus difficiles que nécessaire. Pourquoi pas juste Joinle fil après que vous lui avez signalé de s'arrêter?

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
la source
2
Dans mon cas, je ne contrôle pas les threads sur lesquels s'exécute le truc asynchrone.
intoOrbit
1) Je n'aime pas que vous vérifiiez les touches Ctrl + C, au lieu du signal déclenché par Ctrl + C. 2) Votre approche ne fonctionne pas si l'application utilise des tâches au lieu d'un seul thread de travail.
CodesInChaos
2

Il est également possible de bloquer le thread / programme en fonction d'un jeton d'annulation.

token.WaitHandle.WaitOne();

WaitHandle est signalé lorsque le jeton est annulé.

J'ai vu cette technique utilisée par Microsoft.Azure.WebJobs.JobHost, où le jeton provient d'une source de jeton d'annulation de WebJobsShutdownWatcher (un observateur de fichiers qui met fin au travail).

Cela donne un certain contrôle sur la fin du programme.

CRice
la source
1
C'est une excellente réponse pour toute application de console du monde réel qui a besoin d'écouter CTL+Cparce qu'elle effectue une opération de longue durée, ou est un démon, qui devrait également arrêter ses threads de travail. Vous feriez cela avec un CancelToken et cette réponse tire parti d'un WaitHandlequi existerait déjà, plutôt que d'en créer un nouveau.
mdisibio
1

Des deux, le premier est meilleur

_quitEvent.WaitOne();

parce que dans le second, le thread se réveille toutes les millisecondes sera transformé en interruption du système d'exploitation qui coûte cher

Naveen
la source
C'est une bonne alternative aux Consoleméthodes si vous n'avez pas de console attachée (parce que, par exemple, le programme est démarré par un service)
Marco Sulla
0

Vous devez le faire comme vous le feriez si vous programmiez un service Windows. Vous n'utiliseriez jamais une instruction while à la place, vous utiliseriez un délégué. WaitOne () est généralement utilisé en attendant que les threads se débarrassent - Thread.Sleep () - n'est pas conseillé - Avez-vous pensé à utiliser System.Timers.Timer en utilisant cet événement pour vérifier l'événement d'arrêt?

KenL
la source