CPU友好的无限循环

时间:2011-09-13 12:50:05

标签: c# .net console-application infinite-loop

编写无限循环很简单:

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

但这会破坏CPU的性能。这个执行线程将尽可能多地从CPU的电源中获取。

降低对CPU影响的最佳方法是什么? 添加一些Thread.Sleep(n)应该可以解决问题,但为Sleep()方法设置高超时值可能表示操作系统没有响应。

假设我需要在控制台应用中每分钟执行一项任务。 我需要让Main()在“无限循环”中运行,而计时器将触发将执行该任务的事件。我希望Main()对CPU的影响最小。

您建议采用哪些方法? Sleep()可以,但正如我已经提到的,这可能表明操作系统没有响应。

稍后编辑:

我想更好地解释一下我在寻找什么:

  1. 我需要一个控制台应用程序而不是Windows服务。控制台应用程序可以使用Compact Framework模拟Windows Mobile 6.x系统上的Windows服务。

  2. 只要Windows Mobile设备正在运行,我就需要一种方法让应用程序保持活动状态。

  3. 我们都知道控制台应用程序只要运行静态Main()函数就会运行,因此我需要一种方法来阻止Main()函数退出。

  4. 在特殊情况下(例如:更新应用程序),我需要请求应用程序停止,因此我需要无限循环并测试某些退出条件。例如,这就是Console.ReadLine()对我没用的原因。没有退出条件检查。

  5. 关于上述内容,我仍然希望Main()函数尽可能地具有资源友好性。让我们确定检查退出条件的函数的指纹。

11 个答案:

答案 0 :(得分:51)

要避免无限循环,只需使用WaitHandle即可。要让流程退出外部世界,请使用带有唯一字符串的EventWaitHandle。以下是一个例子。

如果您是第一次启动它,它会简单地每10秒打印一条消息。如果你在同一时间开始程序的第二个实例,它将通知另一个进程优雅地退出并立即退出。此方法的CPU使用率: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.");
}

答案 1 :(得分:9)

您可以使用System.Threading.Timer Class,它可以在给定的时间段内异步执行回调。

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

作为替代方案,有System.Timers.Timer类暴露Elapsed Event,当给定的时间段过去时,它会升起。

答案 2 :(得分:1)

为什么你会宽恕使用无限循环?对于这个例子,将程序设置为计划任务,每分钟运行一次,不是更经济吗?

答案 3 :(得分:1)

为什么不编写一个小应用程序并使用系统的任务调度程序来运行它每分钟,小时......等等?

另一种选择是编写一个在后台运行的Windows服务。该服务可以在MSDN上使用类似以下的简单Alarm类:

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

您可以使用它来定期触发您的方法。在内部,此Alarm类使用计时器:

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

只需正确设置定时器的间隔(例如60000毫秒),它就会定期提升Elapsed事件。将事件处理程序附加到Elapsed事件以执行您的任务。不需要实现“无限循环”只是为了保持应用程序活着。这是由服务处理的。

答案 4 :(得分:1)

听起来我想要Main()进入可中断的循环。为此,必须在某处涉及多个线程(或者您的循环必须定期轮询;但我不在此处讨论该解决方案)。同一应用程序中的另一个线程或另一个进程中的线程必须能够向Main()循环发出信号,告知它应该终止。

如果是这样,那么我认为你想使用ManualResetEventEventWaitHandle。您可以等待该事件,直到它被发出信号(并且信号必须由另一个线程完成)。

例如:

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

答案 5 :(得分:0)

您可以使用Begin-/End-Invoke来屈服于其他线程。 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);
}

您可以这样使用它:

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

这使用了我机器上一个核心的60%(完全空循环)。或者,您可以在循环体中使用此(Source)代码:

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

在我的机器上使用了一个核心的20%。

答案 6 :(得分:0)

阐述CodeInChaos发表的评论:

您可以设置给定的thread's priority。线程被安排根据其优先级执行。用于确定线程执行顺序的调度算法因每个操作系统而异。所有线程默认为“正常”优先级,但如果将循环设置为低;它不应该从设置为正常的线程中窃取时间。

答案 7 :(得分:0)

我为一个必须处理文件的应用程序执行此操作,因为它们被放在文件夹上。你最好的选择是一个计时器(如建议的那样),在“main”的末尾有一个Console.ReadLine()而没有循环。

现在,您关注告诉应用停止:

我也通过一些基本的“文件”监视器完成了这项工作。只需在应用程序的根文件夹中创建文件“quit.txt”(通过我的程序或可能要求它停止的其他应用程序)将使应用程序退出。半代码:

<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可能是这样的:

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

现在您提到过这是(或可能)移动应用程序?您可能没有文件系统观察程序。在这种情况下,也许你只需要“杀死”这个过程(你说“在特殊情况下(比如:更新应用程序),我需要请求应用程序停止”。无论“请求者”阻止它的是谁,都应该简直就是杀了这个过程)

答案 8 :(得分:0)

Timer方法可能是你最好的选择,但是因为你提到了Thread.Sleep,对于类似的问题有一个有趣的Thread.SpinWaitSpinWait struct替代方案,有时可能比短的Thread.Sleep调用更好。

另请参阅此问题:What's the purpose of Thread.SpinWait method?

答案 9 :(得分:0)

这里有很多“高级”答案,但IMO只需使用Thread.Sleep(低值)就足够了。

定时器也是一个解决方案,但定时器背后的代码也是一个无限循环 - 我会假设 - 在经过的时间间隔内触发代码,但它们具有正确的无限循环设置。

如果您需要大睡眠,可以将其切成较小的睡眠状态。

因此,对于非UI应用程序来说,这是一个简单易用的0%CPU解决方案。

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

关于操作系统如何检测应用程序是否无响应。我不知道任何其他测试而不是UI应用程序,其中有方法来检查UI线程是否处理UI代码。很容易发现UI上的线程休眠。 Windows“应用程序无响应”使用简单的本机方法“SendMessageTimeout”来检测应用程序是否具有无响应的UI。

UI应用程序上的任何无限循环都应始终在单独的线程中运行。

答案 10 :(得分:-1)

要保持控制台应用程序正常运行,只需在Console.ReadLine()中的代码末尾添加Main()

如果用户无法终止应用程序,您可以通过以下循环执行此操作:

while (true){
   Console.ReadLine();
}