web-dev-qa-db-ja.com

CPUフレンドリーな無限ループ

無限ループの作成は簡単です。

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

ただし、これによりCPUのパフォーマンスが低下します。この実行スレッドは、CPUの能力を最大限に活用します。

CPUへの影響を減らす最良の方法は何ですか? Thread.Sleep(n)を追加することで問題は解決しますが、Sleep()メソッドに高いタイムアウト値を設定すると、オペレーティングシステムに応答しないアプリケーションが表示される場合があります。

コンソールアプリで1分ごとにタスクを実行する必要があるとしましょう。タイマーがジョブを実行するイベントを起動する間、Main()を「無限ループ」で実行し続ける必要があります。 CPUへの影響を最小限に抑えてMain()を維持したいと思います。

どのような方法をお勧めしますか。 Sleep()は問題ありませんが、すでに述べたように、これはオペレーティングシステムに対する応答しないスレッドを示している可能性があります。

後編集:

私が探しているものをより良く説明したい:

  1. Windowsサービスではなくコンソールアプリが必要です。コンソールアプリは、Compact Frameworkを使用してWindows Mobile 6.xシステム上のWindowsサービスをシミュレートできます。

  2. Windows Mobileデバイスが実行されている限り、アプリを存続させる方法が必要です。

  3. コンソールアプリは静的なMain()関数が実行されている限り実行されることを皆知っているので、Main()関数の終了を防ぐ方法が必要です。

  4. 特別な状況(アプリの更新など)では、アプリの停止を要求する必要があるため、無限にループして終了条件をテストする必要があります。たとえば、これがConsole.ReadLine()が私にとって役に立たない理由です。終了条件のチェックはありません。

  5. 上記に関して、Main()関数を可能な限りリソースに優しいものにしたいと思っています。終了条件をチェックする関数のフィンガープリントを脇に置いておきましょう。

52
Adi

無限ループを回避するには、単にWaitHandleを使用します。プロセスを外界から終了させるには、EventWaitHandleと一意の文字列を使用します。以下に例を示します。

初めて起動すると、10秒ごとにメッセージが簡単に出力されます。その間にプログラムの2番目のインスタンスを開始すると、他のプロセスに正常に終了するよう通知し、すぐに終了します。このアプローチの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.");
}
51
Oliver

System.Threading.Timer Classを使用して、特定の期間に非同期でコールバックを実行する機能を提供できます。

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

別の方法として、 System.Timers.Timer を公開するクラスがあります Elapsed Event は、一定の時間が経過すると発生します。

9
sll

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
Matthew Watson

フォルダーにドロップされたファイルを処理する必要があるアプリケーションに対してこれを行いました。あなたの最善の策は、ループに入れずに「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);
}

さて、あなたはこれがモバイルアプリケーション用である(またはそうである可能性がある)と言いましたか?ファイルシステムウォッチャーがない場合があります。その場合、プロセスを「強制終了」するだけでよい場合があります(「特別な状況(アプリの更新など)で、アプリの停止を要求する必要がある」と言いました。停止する「要求者」は、単にプロセスを強制終了します)

1

なぜ無限ループの使用を容認するのですか?この例では、プログラムをスケジュールされたタスクとして設定し、毎分実行するようにすると、経済的ではなくなりますか?

1
anothershrubery

小さなアプリケーションを作成し、システムのタスクスケジューラを使用して毎分、毎時間などに実行してみませんか?

別のオプションは、バックグラウンドで実行されるWindowsサービスを記述することです。このサービスは、MSDNで次のような単純なAlarmクラスを使用できます。

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

これを使用して、定期的にメソッドをトリガーできます。内部的に、このAlarmクラスはタイマーを使用します。

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

タイマーの間隔(60000ミリ秒など)を正しく設定するだけで、定期的にElapsedイベントが発生します。 Elapsedイベントにイベントハンドラーを接続して、タスクを実行します。アプリケーションを存続させるためだけに「無限ループ」を実装する必要はありません。これはサービスによって処理されます。

1

おそらくタイマーアプローチが最善の策ですが、Thread.Sleepに言及しているため、興味深い Thread.SpinWait または SpinWait struct の代替があります。短いThread.Sleep呼び出し。

この質問も参照してください: Thread.SpinWaitメソッドの目的は何ですか?

0
Zaid Masud

CodeInChaosが行ったコメントを説明するには:

特定の スレッドの優先度 を設定できます。スレッドは、優先度に基づいて実行がスケジュールされます。スレッドの実行順序を決定するために使用されるスケジューリングアルゴリズムは、オペレーティングシステムごとに異なります。すべてのスレッドはデフォルトで「通常」優先度に設定されていますが、ループを低く設定すると、通常に設定されたスレッドから時間を奪ってはなりません。

0
Tremmors

多くの「高度な」回答がここにありますが、ほとんどの場合、IMOはThread.Sleep(lowvalue)を使用するだけで十分です。

タイマーも解決策ですが、タイマーの背後にあるコードは無限ループです-私は推測します-それは経過した間隔であなたのコードを起動しますが、それらは正しい無限ループのセットアップを持っています。

大きな睡眠が必要な場合は、小さな睡眠にカットできます。

したがって、このようなものは、非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);
        }
    }
}

アプリが応答しない場合にOSが検出する方法について。 UIスレッドがUIコードを処理するかどうかを確認するメソッドがあるUIアプリケーション以外のテストは知りません。 UIでのスレッドのスリープは簡単に発見されます。 Windowsの「アプリケーションは応答しません」は、単純なネイティブメソッド「SendMessageTimeout」を使用して、アプリに応答がないUIがあるかどうかを確認します。

UIアプリの無限ループは、常に別のスレッドで実行する必要があります。

0
Wolf5

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

これは、マシン上の1つのコアの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();
}

それは私のマシンの1つのコアの20%を使用していました。

0