it-swarm.it

Loop infinito CPU friendly

Scrivere un ciclo infinito è semplice:

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

Ma questo rovinerà le prestazioni della CPU. Questo thread di esecuzione prenderà il più possibile dalla potenza della CPU.

Qual è il modo migliore per ridurre l'impatto sulla CPU? L'aggiunta di alcuni Thread.Sleep(n) dovrebbe fare il trucco, ma l'impostazione di un valore di timeout elevato per il metodo Sleep() può indicare un'applicazione non rispondente al sistema operativo.

Diciamo che devo eseguire un'attività ogni minuto circa in un'app console. Devo mantenere Main() in esecuzione in un "ciclo infinito" mentre un timer attiverà l'evento che farà il lavoro. Vorrei mantenere Main() con il minor impatto sulla CPU.

Quali metodi suggerisci. Sleep() può essere ok, ma come ho già detto, questo potrebbe indicare un thread non rispondente al sistema operativo.

LATER EDIT:

Voglio spiegare meglio cosa sto cercando:

  1. Ho bisogno di un'app console non del servizio Windows. Le app della console possono simulare i servizi di Windows su sistemi Windows Mobile 6.x con Compact Framework.

  2. Ho bisogno di un modo per mantenere viva l'app finché il dispositivo Windows Mobile è in esecuzione.

  3. Sappiamo tutti che l'app della console funziona finché funziona la sua funzione Main () statica, quindi ho bisogno di un modo per impedire l'uscita dalla funzione Main ().

  4. In situazioni speciali (come: aggiornamento dell'app), devo richiedere l'app per fermarsi, quindi ho bisogno di eseguire un ciclo continuo e testare alcune condizioni di uscita. Ad esempio, questo è il motivo per cui Console.ReadLine() non è utile per me. Non esiste un controllo delle condizioni di uscita.

  5. Per quanto riguarda quanto sopra, voglio ancora che Main () funzioni il più possibile in termini di risorse. Consentire l'impronta digitale della funzione che verifica la condizione di uscita.

52
Adi

Per evitare il ciclo infinito basta usare un WaitHandle. Per consentire al processo di uscire dal mondo esterno utilizzare un EventWaitHandle con una stringa univoca. Di seguito è riportato un esempio.

Se lo avvii per la prima volta, stampa semplicemente un messaggio ogni 10 secondi. Se avvii nel frattempo una seconda istanza del programma, informerà l'altro processo di uscire con grazia e si chiude anche immediatamente. L'utilizzo della CPU per questo approccio: 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

È possibile utilizzare System.Threading.Timer Classe che consente di eseguire la richiamata in modo asincrono in un determinato periodo di tempo.

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

In alternativa c'è System.Timers.Timer classe che espone Evento trascorso che aumenta quando è trascorso un determinato periodo di tempo.

9
sll

Mi sembra che tu voglia che Main () entri in un loop interrompibile. Affinché ciò avvenga, è necessario coinvolgere più thread da qualche parte (o il ciclo deve eseguire periodicamente il polling; non sto discutendo questa soluzione qui). Un altro thread nella stessa applicazione o un thread in un altro processo, devono essere in grado di segnalare al loop Main () che deve terminare.

Se questo è vero, penso che tu voglia usare un ManualResetEvent o un EventWaitHandle . Puoi attendere quell'evento fino a quando non viene segnalato (e la segnalazione dovrebbe essere effettuata da un altro thread).

Per esempio:

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

L'ho fatto per un'applicazione che doveva elaborare i file mentre venivano rilasciati in una cartella. La tua scommessa migliore è un timer (come suggerito) con una Console.ReadLine () alla fine di "main" senza mettere in loop.

Ora, la tua preoccupazione di dire all'app di smettere:

Ho anche fatto questo tramite un rudimentale monitor "file". Semplicemente creando il file "quit.txt" nella cartella principale dell'applicazione (dal mio programma o da un'altra applicazione che potrebbe richiederne l'arresto), l'applicazione verrà chiusa. 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();

OnNewFile potrebbe essere qualcosa del genere:

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);
}

Ora hai detto che questo è (o potrebbe essere) per un'applicazione mobile? Potresti non avere il watcher del file system. In tal caso, forse hai solo bisogno di "uccidere" il processo (hai detto "In situazioni speciali (come: aggiornamento dell'app), ho bisogno di richiedere l'app per interrompere". Chiunque sia il "richiedente" per fermarlo, dovrebbe semplicemente uccidi il processo)

1
Nelson Rodriguez

Perché dovresti perdonare l'uso di un ciclo infinito? Per questo esempio, l'impostazione del programma come attività pianificata, da eseguire ogni minuto, non sarebbe più economica?

1
anothershrubery

Perché non scrivere una piccola applicazione e utilizzare l'utilità di pianificazione del sistema per eseguirla ogni minuto, ora ... ecc?

Un'altra opzione sarebbe quella di scrivere un servizio di Windows che viene eseguito in background. Il servizio potrebbe utilizzare una semplice classe di allarme come la seguente su MSDN:

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

Puoi usarlo per attivare periodicamente il tuo metodo. Internamente questa classe di allarme utilizza un timer:

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

Basta impostare correttamente l'intervallo del timer (ad es. 60000 millisecondi) e aumenterà periodicamente l'evento trascorso. Collegare un gestore eventi all'evento trascorso per eseguire l'attività. Non è necessario implementare un "ciclo infinito" solo per mantenere viva l'applicazione. Questo è gestito per te dal servizio.

1
Christophe Geers

L'approccio Timer è probabilmente la soluzione migliore, ma dal momento che menzioni Thread.Sleep c'è un'interessante alternativa Thread.SpinWait o SpinWait struct per problemi simili che a volte possono essere migliori di Discussione breve. Invocazioni notturne.

Vedi anche questa domanda: Qual è lo scopo del metodo Thread.SpinWait?

0
Zaid Masud

Per spiegare un commento fatto da CodeInChaos:

Puoi impostare un dato priorità del thread . Le discussioni sono programmate per l'esecuzione in base alla loro priorità. L'algoritmo di pianificazione utilizzato per determinare l'ordine di esecuzione del thread varia a seconda del sistema operativo. Tutti i thread passano automaticamente alla priorità "normale", ma se si imposta il loop su basso; non dovrebbe rubare tempo dai thread impostati alla normalità.

0
Tremmors

Molte risposte "avanzate" qui, ma IMO semplicemente usando un Thread.Sleep (valore basso) dovrebbe essere sufficiente per la maggior parte.

Anche i timer sono una soluzione, ma il codice dietro un timer è anche un ciclo infinito - suppongo - che spara il tuo codice su intervalli trascorsi, ma hanno la corretta configurazione del ciclo infinito.

Se hai bisogno di dormire molto, puoi tagliarlo in piccoli dormienti.

Quindi qualcosa del genere è una soluzione CPU allo 0% semplice e facile per un'app 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);
        }
    }
}

Per quanto riguarda il modo in cui il sistema operativo rileva se l'app non risponde. Non conosco altri test oltre alle applicazioni dell'interfaccia utente, in cui esistono metodi per verificare se il thread dell'interfaccia utente elabora il codice dell'interfaccia utente. Il thread si interrompe sull'interfaccia utente sarà facilmente scoperto. "L'applicazione non risponde" di Windows utilizza un semplice metodo nativo "SendMessageTimeout" per verificare se l'app ha un'interfaccia utente non rispondente.

Qualsiasi ciclo infinito su un'app UI deve sempre essere eseguito in un thread separato.

0
Wolf5

Puoi usare Begin-/End-Invoke per cedere ad altri thread. Per esempio.

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);
}

Lo useresti come tale:

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

Questo ha utilizzato il 60% di un core sulla mia macchina (loop completamente vuoto). In alternativa, puoi usare questo codice ( Source ) nel corpo del tuo loop:

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();
}

Che ha usato il 20% di un core sulla mia macchina.

0