Дружественный к процессору бесконечный цикл - PullRequest
52 голосов
/ 13 сентября 2011

Запись бесконечного цикла проста:

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

Но это снизит производительность процессора.Этот поток выполнения будет максимально использовать мощность процессора.

Каков наилучший способ снизить воздействие на процессор?Добавление некоторого Thread.Sleep(n) должно помочь, но установка высокого значения времени ожидания для метода Sleep() может указывать на не отвечающее приложение в операционной системе.

Допустим, мне нужно выполнять задачу каждую минуту или около того вконсольное приложение.Мне нужно, чтобы Main() работал в «бесконечном цикле», пока таймер сработает, выполнив эту работу.Я хотел бы сохранить Main() с минимальным воздействием на процессор.

Какие методы вы предлагаете.Sleep() может быть в порядке, но, как я уже говорил, это может указывать на не отвечающий поток в операционной системе.

ПОСЛЕДНЕЕ РЕДАКТИРОВАНИЕ:

Я хочу объяснить лучшечто я ищу:

  1. Мне нужно консольное приложение, а не служба Windows.Консольные приложения могут имитировать службы Windows в системах Windows Mobile 6.x с помощью Compact Framework.

  2. Мне нужен способ сохранить приложение живым, пока работает устройство Windows Mobile.

  3. Мы все знаем, что консольное приложение работает до тех пор, пока работает его статическая функция Main (), поэтому мне нужен способ предотвратить выход из функции Main ().

  4. В особых ситуациях (например, обновление приложения) мне нужно запросить остановку приложения, поэтому мне нужно бесконечно зацикливаться и проверять некоторые условия выхода.Например, именно поэтому Console.ReadLine() мне не нужен.Нет проверки состояния выхода.

  5. Что касается вышеизложенного, я все еще хочу, чтобы функция Main () была максимально дружественной к ресурсам.Давайте выделим отпечаток функции, которая проверяет условие выхода.

Ответы [ 11 ]

51 голосов
/ 11 сентября 2012

Чтобы избежать бесконечного цикла, просто используйте WaitHandle. Чтобы разрешить процессу выйти из внешнего мира, используйте EventWaitHandle с уникальной строкой. Ниже приведен пример.

Если вы запустите его в первый раз, он просто печатает сообщение каждые 10 секунд. Если вы тем временем запускаете второй экземпляр программы, он сообщит другому процессу о том, что он грациозно завершит работу, и завершит себя также немедленно. Использование ЦП для этого подхода: 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.");
}
9 голосов
/ 13 сентября 2011

Вы можете использовать System.Threading.Timer Класс, который предоставляет возможность асинхронного выполнения обратного вызова в течение заданного периода времени.

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

В качестве альтернативы существует System.Timers.Timer класс, который выставляет Истекшее событие , который возникает, когда истекает определенный период времени.

1 голос
/ 11 сентября 2012

Мне кажется, что вы хотите, чтобы 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");
    }
}
1 голос
/ 19 мая 2012

Я сделал это для приложения, которое должно было обрабатывать файлы, когда они помещались в папку. Ваша лучшая ставка - это таймер (как предложено) с Console.ReadLine () в конце «main» без зацикливания.

Теперь, ваше беспокойство по поводу остановки приложения:

Я также сделал это через какой-то элементарный «файловый» монитор. Простое создание файла «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);
}

Теперь вы упомянули, что это (или может быть) для мобильного приложения? Возможно, у вас нет наблюдателя за файловой системой. В этом случае, возможно, вам просто нужно «убить» процесс (вы сказали «В особых ситуациях (например, обновление приложения), мне нужно запросить приложение на остановку». Кто бы ни был «запрашивающий», чтобы остановить его, просто убить процесс)

1 голос
/ 13 сентября 2011

Почему бы вам не написать небольшое приложение и использовать системный планировщик задач, чтобы запускать его каждую минуту, час ... и т. Д.?

Другой вариант - написать службу Windows, которая работает в фоновом режиме. Служба может использовать простой класс Alarm, например, следующий в MSDN:

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, чтобы выполнить вашу задачу. Не нужно реализовывать «бесконечный цикл» только для того, чтобы приложение оставалось в живых. Это обрабатывается для вас службой.

1 голос
/ 13 сентября 2011

Почему вы потворствуете использованию бесконечного цикла? Для этого примера установка программы как запланированной задачи, которая будет выполняться каждую минуту, не будет более экономичной?

0 голосов
/ 20 мая 2016

Здесь много «расширенных» ответов, но для большинства IMO достаточно просто использовать Thread.Sleep (lowvalue).

Таймеры также являются решением, но код таймера также является бесконечным циклом - я бы предположил - который запускает ваш код с истекшими интервалами, но у них правильная настройка бесконечного цикла.

Если вам нужен большой сон, вы можете разделить его на более мелкие.

Итак, что-то вроде этого - это простое и легкое 0% -ное решение ЦП для приложения без пользовательского интерфейса.

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

Относительно того, как ОС обнаруживает, что приложение не отвечает. Я не знаю каких-либо других тестов, кроме приложений пользовательского интерфейса, где есть методы, чтобы проверить, обрабатывает ли поток пользовательского интерфейса код пользовательского интерфейса. Поток спит на пользовательском интерфейсе будет легко обнаружен. Windows «Приложение не отвечает» использует простой собственный метод «SendMessageTimeout», чтобы определить, есть ли в приложении неподтвержденный пользовательский интерфейс.

Любой бесконечный цикл в приложении пользовательского интерфейса всегда должен выполняться в отдельном потоке.

0 голосов
/ 11 сентября 2012

Подход с использованием таймера, вероятно, является лучшим выбором, но поскольку вы упоминаете Thread.Sleep, есть интересная альтернатива Thread.SpinWait или SpinWait struct для подобных проблем, которые иногда могут быть лучшечем короткие вызовы Thread.Sleep.

См. также этот вопрос: Для чего нужен метод Thread.SpinWait?

0 голосов
/ 13 сентября 2011

Изложить в комментарии CodeInChaos сделал:

Вы можете установить данный приоритет потока . Потоки запланированы для выполнения в зависимости от их приоритета. Алгоритм планирования, используемый для определения порядка выполнения потока, зависит от конкретной операционной системы. Все потоки по умолчанию имеют «нормальный» приоритет, но если вы установите низкий цикл; он не должен красть время из потоков, установленных в нормальное состояние.

0 голосов
/ 13 сентября 2011

Вы можете использовать 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% одного ядра на моей машине (полностью пустой цикл)В качестве альтернативы вы можете использовать этот ( 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% одного ядра на моей машине.

...