CPU friendly infinite loop

2019-01-21 03:57发布

Writing an infinite loop is simple:

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

But this will trash the CPU performance. This execution thread will take as much as possible from CPU's power.

What is the best way to lower the impact on CPU? Adding some Thread.Sleep(n) should do the trick, but setting a high timeout value for Sleep() method may indicate an unresponsive application to the operating system.

Let's say I need to perform a task each minute or so in a console app. I need to keep Main() running in an "infinite loop" while a timer will fire the event that will do the job. I would like to keep Main() with the lowest impact on CPU.

What methods do you suggest. Sleep() can be ok, but as I already mentioned, this might indicate an unresponsive thread to the operating system.

LATER EDIT:

I want to explain better what I am looking for:

  1. I need a console app not Windows service. Console apps can simulate the Windows services on Windows Mobile 6.x systems with Compact Framework.

  2. I need a way to keep the app alive as long as the Windows Mobile device is running.

  3. We all know that the console app runs as long as its static Main() function runs, so I need a way to prevent Main() function exit.

  4. In special situations (like: updating the app), I need to request the app to stop, so I need to infinitely loop and test for some exit condition. For example, this is why Console.ReadLine() is no use for me. There is no exit condition check.

  5. Regarding the above, I still want Main() function as resource friendly as possible. Let asside the fingerprint of the function that checks for the exit condition.

11条回答
The star\"
2楼-- · 2019-01-21 04:37

To keep console applications running just add a Console.ReadLine() to the end of your code in Main().

If the user shouldn't be able to terminate the application you can do this with a loop like the following:

while (true){
   Console.ReadLine();
}
查看更多
一夜七次
3楼-- · 2019-01-21 04:38

To avoid the infinity loop simply use a WaitHandle. To let the process be exited from the outer world use a EventWaitHandle with a unique string. Below is an example.

If you start it the first time, it simple prints out a message every 10 seconds. If you start in the mean time a second instance of the program it will inform the other process to gracefully exit and exits itself also immediately. The CPU usage for this approach: 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.");
}
查看更多
Deceive 欺骗
4楼-- · 2019-01-21 04:38

Why don't you write a small application and use the system's task scheduler to run it every minute, hour...etc?

Another option would be to write a Windows Service which runs in the background. The service could use a simple Alarm class like the following on MSDN:

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

You can use it to periodically trigger your method. Internally this Alarm class uses a timer:

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

Just set the timer's interval correctly (e.g. 60000 milliseconds) and it will raise the Elapsed event periodically. Attach an event handler to the Elapsed event to perform your task. No need to implement an "infinite loop" just to keep the application alive. This is handled for you by the service.

查看更多
我欲成王,谁敢阻挡
5楼-- · 2019-01-21 04:38

It sounds to me like you want Main() to enter an interruptable loop. For this to happen, multiple threads must be involved somewhere (or your loop must poll periodically; I am not discussing that solution here though). Either another thread in the same application, or a thread in another process, must be able to signal to your Main() loop that it should terminate.

If this is true, then I think you want to use a ManualResetEvent or an EventWaitHandle . You can wait on that event until it is signalled (and the signalling would have to be done by another thread).

For example:

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");
    }
}
查看更多
Luminary・发光体
6楼-- · 2019-01-21 04:43

You can use Begin-/End-Invoke to yield to other threads. E.g.

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

You would use it as such:

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

This used 60% of one core on my machine (completely empty loop). Alternatively, you can use this (Source) code in the body of your 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();
}

That used 20% of one core on my machine.

查看更多
登录 后发表回答