CPU友好的无限循环

本文关键字:无限循环 CPU | 更新日期: 2023-09-27 18:08:22

写一个无限循环很简单:

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

但是这会降低CPU性能。这个执行线程将尽可能多地占用CPU的能量。

降低对CPU影响的最佳方法是什么?添加一些Thread.Sleep(n)应该可以达到目的,但是为Sleep()方法设置一个高超时值可能表明应用程序对操作系统没有响应。

假设我需要在控制台应用程序中每分钟执行一个任务。我需要保持Main()在"无限循环"中运行,而计时器将触发将完成这项工作的事件。我希望Main()对CPU的影响最小。

你建议用什么方法?Sleep()可以,但正如我已经提到的,这可能表示对操作系统没有响应的线程。

后来编辑:

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

  1. 我需要一个控制台应用程序,而不是Windows服务。主机应用程序可以模拟Windows Mobile 6上的Windows服务。

  2. 我需要一种方法来保持应用程序存活只要Windows Mobile设备正在运行

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

  4. 在特殊情况下(如:更新应用程序),我需要请求应用程序停止,所以我需要无限循环和测试一些退出条件。例如,这就是为什么Console.ReadLine()对我没用。没有退出条件检查

  5. 关于以上,我仍然希望Main()函数尽可能地资源友好。把检查退出条件的函数的指纹放在一边。

CPU友好的无限循环

要避免无限循环,只需使用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.");
}

您可以使用System.Threading.Timer类,它提供了在给定时间段内异步执行回调的能力。

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

还有System.Timers.Timer类,它公开了当给定的一段时间过去时引发的Elapsed Event。

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

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

另一个选择是编写一个在后台运行的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事件以执行任务。不需要实现一个"无限循环"来保持应用程序的存活。这是由服务部门为您处理的。

我这样做是为了一个必须在文件放到文件夹中时处理文件的应用程序。你最好的选择是在"main"的末尾使用一个带有Console.ReadLine()的计时器,而不使用循环。

现在,告诉应用程序停止的问题:

我也通过一些基本的"文件"监视器完成了这一点。只需在应用程序的根文件夹中创建文件"quit.txt"(通过我的程序或其他可能请求它停止的应用程序)就可以使应用程序退出。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可以是这样的:

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

现在你提到这是(或可能是)一个移动应用程序?您可能没有文件系统监视程序。在这种情况下,也许你只需要"杀死"进程(你说"在特殊情况下(如更新应用),我需要请求应用停止")。无论"请求者"是谁,都应该直接终止该进程)

听起来你想让Main()进入一个可中断的循环。要做到这一点,必须在某个地方涉及多个线程(或者循环必须定期轮询;我不是在这里讨论这个解决方案)。无论是同一应用程序中的另一个线程,还是另一个进程中的线程,都必须能够向Main()循环发出终止信号。

如果这是真的,那么我认为你想使用一个ManualResetEvent或一个EventWaitHandle。你可以等待那个事件,直到它被发出信号(而信号必须由另一个线程完成)。

例如:

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

您可以使用Begin-/End-Invoke来让步给其他线程。例如

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%(完全空循环)。或者,您可以在循环体中使用以下(源代码)代码:

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%。

详细说明CodeInChaos的评论:

你可以设置一个线程的优先级。线程是根据它们的优先级调度执行的。用于确定线程执行顺序的调度算法因操作系统而异。所有线程默认为"正常"优先级,但如果你将循环设置为低;它不应该从设置为normal的线程中窃取时间。

Timer方法可能是您最好的选择,但是既然您提到了Thread。睡眠有一个有趣的线索。SpinWait或SpinWait结构体替代类似的问题,有时可能比短线程更好。睡眠调用。

参见这个问题:Thread的目的是什么?SpinWait方法?

这里有很多"高级"的答案,但我认为简单地使用Thread.Sleep(low - value)应该足够了。

计时器也是一种解决方案,但是计时器背后的代码也是一个无限循环——我认为——在经过的间隔上触发你的代码,但是它们有正确的无限循环设置。

如果你需要长时间的睡眠,你可以把它分成更小的睡眠。

所以像这样的东西对于一个非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应用程序上的任何无限循环都应该在单独的线程中运行。

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

如果用户不能终止应用程序,可以使用如下循环:

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