web-dev-qa-db-fra.com

Boucle infinie compatible CPU

Écrire une boucle infinie est simple:

while(true){
    //add whatever break condition here
}

Mais cela détruira les performances du processeur. Ce thread d'exécution prendra autant que possible de la puissance du processeur.

Quelle est la meilleure façon de réduire l'impact sur le processeur? L'ajout de Thread.Sleep(n) devrait suffire, mais la définition d'un délai d'expiration élevé pour la méthode Sleep() peut indiquer une application qui ne répond pas au système d'exploitation.

Disons que je dois effectuer une tâche toutes les minutes environ dans une application console. J'ai besoin de maintenir Main() en cours d'exécution dans une "boucle infinie" pendant qu'un minuteur déclenchera l'événement qui fera le travail. Je voudrais conserver Main() avec le plus faible impact sur le CPU.

Quelles méthodes proposez-vous? Sleep() peut être ok, mais comme je l'ai déjà mentionné, cela peut indiquer un thread qui ne répond pas au système d'exploitation.

MODIFICATION PLUS TARD:

Je veux mieux expliquer ce que je recherche:

  1. J'ai besoin d'une application console et non d'un service Windows. Les applications de console peuvent simuler les services Windows sur les systèmes Windows Mobile 6.x avec Compact Framework.

  2. J'ai besoin d'un moyen de maintenir l'application en vie tant que l'appareil Windows Mobile est en cours d'exécution.

  3. Nous savons tous que l'application console fonctionne aussi longtemps que sa fonction statique Main () s'exécute, j'ai donc besoin d'un moyen d'empêcher la sortie de la fonction Main ().

  4. Dans des situations spéciales (comme la mise à jour de l'application), je dois demander à l'application de s'arrêter, j'ai donc besoin de faire une boucle infinie et de tester une condition de sortie. Par exemple, c'est pourquoi Console.ReadLine() ne me sert à rien. Il n'y a pas de vérification des conditions de sortie.

  5. Concernant ce qui précède, je veux toujours que la fonction Main () soit aussi conviviale que possible. Assidons l'empreinte de la fonction qui vérifie la condition de sortie.

52
Adi

Pour éviter la boucle infinie, utilisez simplement un WaitHandle. Pour laisser le processus quitter le monde extérieur, utilisez un EventWaitHandle avec une chaîne unique. Voici un exemple.

Si vous le démarrez la première fois, il imprime simplement un message toutes les 10 secondes. Si vous lancez dans le même temps une seconde instance du programme, il informera l'autre processus de se terminer correctement et se fermera également immédiatement. L'utilisation du processeur pour cette approche: 0%

private static void Main(string[] args)
{
    // Create a IPC wait handle with a unique identifier.
    bool createdNew;
    var waitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, "CF2D4313-33DE-489D-9721-6AFF69841DEA", out createdNew);
    var signaled = false;

    // If the handle was already there, inform the other process to exit itself.
    // Afterwards we'll also die.
    if (!createdNew)
    {
        Log("Inform other process to stop.");
        waitHandle.Set();
        Log("Informer exited.");

        return;
    }

    // Start a another thread that does something every 10 seconds.
    var timer = new Timer(OnTimerElapsed, null, TimeSpan.Zero, TimeSpan.FromSeconds(10));

    // Wait if someone tells us to die or do every five seconds something else.
    do
    {
        signaled = waitHandle.WaitOne(TimeSpan.FromSeconds(5));
        // ToDo: Something else if desired.
    } while (!signaled);

    // The above loop with an interceptor could also be replaced by an endless waiter
    //waitHandle.WaitOne();

    Log("Got signal to kill myself.");
}

private static void Log(string message)
{
    Console.WriteLine(DateTime.Now + ": " + message);
}

private static void OnTimerElapsed(object state)
{
    Log("Timer elapsed.");
}
51
Oliver

Vous pouvez utiliser System.Threading.Timer Classe qui offre la possibilité d'exécuter un rappel de manière asynchrone dans une période de temps donnée.

public Timer(
    TimerCallback callback,
    Object state,
    int dueTime,
    int period
)

Comme alternative, il existe System.Timers.Timer classe qui expose Elapsed Event qui se déclenche lorsqu'une période de temps donnée est écoulée.

9
sll

Il me semble que vous voulez que Main () entre dans une boucle interruptible. Pour que cela se produise, plusieurs threads doivent être impliqués quelque part (ou votre boucle doit interroger périodiquement; je ne parle pas de cette solution ici cependant). Soit un autre thread dans la même application, soit un thread dans un autre processus, doit être en mesure de signaler à votre boucle Main () qu'il doit se terminer.

Si cela est vrai, je pense que vous souhaitez utiliser un ManualResetEvent ou un EventWaitHandle . Vous pouvez attendre cet événement jusqu'à ce qu'il soit signalé (et la signalisation devrait être effectuée par un autre thread).

Par exemple:

using System;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    class Program
    {
        static void Main(string[] args)
        {
            startThreadThatSignalsTerminatorAfterSomeTime();
            Console.WriteLine("Waiting for terminator to be signalled.");
            waitForTerminatorToBeSignalled();
            Console.WriteLine("Finished waiting.");
            Console.ReadLine();
        }

        private static void waitForTerminatorToBeSignalled()
        {
            _terminator.WaitOne(); // Waits forever, but you can specify a timeout if needed.
        }

        private static void startThreadThatSignalsTerminatorAfterSomeTime()
        {
            // Instead of this thread signalling the event, a thread in a completely
            // different process could do so.

            Task.Factory.StartNew(() =>
            {
                Thread.Sleep(5000);
                _terminator.Set();
            });
        }

        // I'm using an EventWaitHandle rather than a ManualResetEvent because that can be named and therefore
        // used by threads in a different process. For intra-process use you can use a ManualResetEvent, which 
        // uses slightly fewer resources and so may be a better choice.

        static readonly EventWaitHandle _terminator = new EventWaitHandle(false, EventResetMode.ManualReset, "MyEventName");
    }
}
1
Matthew Watson

Je l'ai fait pour une application qui devait traiter des fichiers car ils étaient déposés dans un dossier. Votre meilleur pari est un chronomètre (comme suggéré) avec une Console.ReadLine () à la fin de "main" sans mettre en boucle.

Maintenant, votre souci de dire à l'application d'arrêter:

J'ai également fait cela via un moniteur "fichier" rudimentaire. La simple création du fichier "quit.txt" dans le dossier racine de l'application (soit par mon programme, soit par une autre application qui pourrait demander son arrêt) entraînera la fermeture de l'application. Semi-code:

<do your timer thing here>
watcher = new FileSystemWatcher();
watcher.Path = <path of your application or other known accessible path>;
watcher.Changed += new FileSystemEventHandler(OnNewFile);
Console.ReadLine();

Le OnNewFile pourrait ressembler à ceci:

private static void OnNewFile(object source, FileSystemEventArgs e)
{
    if(System.IO.Path.GetFileName(e.FullPath)).ToLower()=="quit.txt")
        ... remove current quit.txt
        Environment.Exit(1);
}

Maintenant, vous avez mentionné qu'il s'agit (ou pourrait l'être) d'une application mobile? Vous n'avez peut-être pas l'observateur du système de fichiers. Dans ce cas, vous avez peut-être juste besoin de "tuer" le processus (vous avez dit "Dans des situations spéciales (comme: la mise à jour de l'application), je dois demander à l'application de s'arrêter". Celui qui le "demandeur" doit arrêter doit tuez simplement le processus)

1
Nelson Rodriguez

Pourquoi accepteriez-vous l'utilisation d'une boucle infinie? Pour cet exemple, la configuration du programme en tant que tâche planifiée, à exécuter toutes les minutes, ne serait-elle pas plus économique?

1
anothershrubery

Pourquoi n'écrivez-vous pas une petite application et n'utilisez pas le planificateur de tâches du système pour l'exécuter toutes les minutes, heures ... etc?

Une autre option serait d'écrire un service Windows qui s'exécute en arrière-plan. Le service peut utiliser une classe d'alarme simple comme celle-ci sur MSDN:

http://msdn.Microsoft.com/en-us/library/wkzf914z%28v=VS.90%29.aspx#Y24

Vous pouvez l'utiliser pour déclencher périodiquement votre méthode. En interne, cette classe d'alarme utilise une minuterie:

http://msdn.Microsoft.com/en-us/library/system.timers.timer.aspx

Réglez simplement l'intervalle du temporisateur correctement (par exemple 60000 millisecondes) et il augmentera périodiquement l'événement Elapsed. Attachez un gestionnaire d'événements à l'événement Elapsed pour effectuer votre tâche. Pas besoin d'implémenter une "boucle infinie" juste pour garder l'application en vie. Ceci est géré pour vous par le service.

1
Christophe Geers

L'approche Timer est probablement votre meilleur pari, mais puisque vous mentionnez Thread.Sleep, il existe une alternative intéressante Thread.SpinWait ou SpinWait struct pour des problèmes similaires qui peuvent parfois être meilleurs que invocations Thread.Sleep courtes.

Voir également cette question: Quel est le but de la méthode Thread.SpinWait?

0
Zaid Masud

Pour exposer un commentaire que CodeInChaos a fait:

Vous pouvez définir la priorité d'un thread donné . Les threads doivent être exécutés en fonction de leur priorité. L'algorithme de planification utilisé pour déterminer l'ordre d'exécution des threads varie selon chaque système d'exploitation. Tous les threads ont par défaut la priorité "normale", mais si vous définissez votre boucle sur faible; il ne devrait pas voler le temps des threads mis à la normale.

0
Tremmors

Beaucoup de réponses "avancées" ici, mais IMO utilisant simplement un Thread.Sleep (lowvalue) devrait suffire pour la plupart.

Les minuteries sont également une solution, mais le code derrière une minuterie est également une boucle infinie - je suppose - qui déclenche votre code sur des intervalles de temps écoulés, mais ils ont la configuration de boucle infinie correcte.

Si vous avez besoin d'un sommeil de grande taille, vous pouvez le couper en plus petits sommeil.

Donc, quelque chose comme ça est une solution CPU 0% simple et facile pour une application non UI.

static void Main(string[] args)
{
    bool wait = true;
    int sleepLen = 1 * 60 * 1000; // 1 minute
    while (wait)
    {

        //... your code

        var sleepCount = sleepLen / 100;
        for (int i = 0; i < sleepCount; i++)
        {
            Thread.Sleep(100);
        }
    }
}

En ce qui concerne la façon dont le système d'exploitation détecte si l'application ne répond pas. Je ne connais aucun autre test que sur les applications d'interface utilisateur, où il existe des méthodes pour vérifier si le thread d'interface utilisateur traite le code d'interface utilisateur. Les threads en veille sur l'interface utilisateur seront facilement découverts. L'application "Windows ne répond pas" de Windows utilise une méthode native simple "SendMessageTimeout" pour voir si l'application a une interface utilisateur sans réponse.

Toute boucle infinie sur une application d'interface utilisateur doit toujours être exécutée dans un thread distinct.

0
Wolf5

Vous pouvez utiliser Begin-/End-Invoke pour céder la place à d'autres threads. Par exemple.

public static void ExecuteAsyncLoop(Func<bool> loopBody)
{
    loopBody.BeginInvoke(ExecuteAsyncLoop, loopBody);
}

private static void ExecuteAsyncLoop(IAsyncResult result)
{
    var func = ((Func<bool>)result.AsyncState);
    try
    {
        if (!func.EndInvoke(result))
            return;
    }
    catch
    {
        // Do something with exception.
        return;
    }

    func.BeginInvoke(ExecuteAsyncLoop, func);
}

Vous l'utiliseriez comme tel:

ExecuteAsyncLoop(() =>
    {
        // Do something.
        return true; // Loop indefinitely.
    });

Cela a utilisé 60% d'un cœur sur ma machine (boucle complètement vide). Alternativement, vous pouvez utiliser ce code ( Source ) dans le corps de votre boucle:

private static readonly bool IsSingleCpuMachine = (Environment.ProcessorCount == 1);
[DllImport("kernel32", ExactSpelling = true)]
private static extern void SwitchToThread();

private static void StallThread()
{
    // On a single-CPU system, spinning does no good
    if (IsSingleCpuMachine) SwitchToThread();
    // Multi-CPU system might be hyper-threaded, let other thread run
    else Thread.SpinWait(1);
}

while (true)
{
    // Do something.
    StallThread();
}

Cela a utilisé 20% d'un cœur sur ma machine.

0
Jonathan Dickinson