C#で一定間隔で処理を実行したい場合の待機方法。
まず基本
await Task.Delay(TimeSpan.FromSeconds(3));
私は最近、ある程度長い時間(100ms以上)待機するときは、TimeSpan使っています。
100msを超えるような間隔の場合、特に問題ないのですが、これが5ms間隔とかになってくると問題が起きます。
await Task.Delay(5); // 5ms
これを実行すると、15msほど待機してしまいます。
これは、ドキュメントによると仕様とのことです。
ちょっと寄り道して・・・
var results = new List<DateTime>(10000);
for(int i = 0;i<100;i++)
{
var nextTime = DateTime.UtcNow.AddMilliseconds(5);
while(nextTime > DateTime.UtcNow)
{
await Task.Yield();
}
results.Add(DateTime.UtcNow);
await Task.Delay(1);
results.Add(DateTime.UtcNow);
}
こんなコードを書いてテストしてみました。
この場合、Task.Delayで待つ時間は10msほどになりました。
Task.Delayから15msというわけではなくて、15ms間隔でタイマーが動いていて、次のタイマーまで待つという挙動をするようですね。
このテストコードで書いたように、目的の時間になるまで、Task.Yeild()をしながら待つコードにすると、
自分のプロセスの他の非同期のタスクを止めずに、目的の時間を待つことができます。待つ時間は、マイクロ秒とかでも対応できます。
ただ、このプログラム、待っている間ずっとCPUが動いているので、待っている間、CPUが100%になってしまう問題があります。
Thred.Sleepを使って待つこともできますが、この場合はCPU負荷は問題になりませんが、自プロセスの他のタスクがある場合、その動作が止まってしまう問題があります。
(2つ以上のスレッドがあれば、他のスレッドは動くでしょうが)
さて、上記を踏まえて、今のところの最終案
var waitTime = _nextTime - DateTime.UtcNow - TimeSpan.FromMilliseconds(15);
if (waitTime > TimeSpan.Zero) { await Task.Delay(waitTime).ConfigureAwait(false); }
while (true)
{
waitTime = _nextTime - DateTime.UtcNow;
if (waitTime <= TimeSpan.Zero) { break; }
await Task.Yield();
}
15ms少なくTask.Delayで待って、最後の部分は、Task.Yieldのループで待つ。
Task.Yieldループで待つ部分は、CPU100%を許容。
間隔が15ms以内の場合は、ずっとCPUが100%になっちゃうね。うーん。困った。
今のところ、CPU負荷を高くせずに、数ミリ秒,マイクロ秒の待ちを達成する方法が見つかっていません。
どなたがご存じの方がおりましたら、コメントなどで教えていただけると助かります。