3

So for a project of mine I need to fetch values from a piece of hardware every 100 ms. The fetching takes x amount of milliseconds. So what I'm looking for but cannot find is a way to make sure a for/while always takes 100 ms (or more).

So in pseudo code:

for (int i = 0; i < 100; i++) // Take measurements for 10 seconds
{
    // Call some async method that sleeps for 100 ms
    this.temperatureList.Add(this.HardwareManager.GetSensorValue(ESensor.Temperature)); // Takes 15 ms
    this.temperatureList.Add(this.HardwareManager.GetSensorValue(ESensor.Pressure)); // Takes 30 ms
    // await async method to finish
}

I have tried some async await things with this but I cannot seem to comprehend it. Anyone who can help me find a reliable way to get 10 seconds of measurements with an interval of 100 ms?

EDIT: I know that since I'm running C# on Windows, timing is not really a valid option. But this does not really matter in my question. My question is, how can I make sure the for loop takes at least(!!!) a 100 ms, while being as close as possible to 100 ms.

Foitn
  • 604
  • 6
  • 25
  • Use a timer with an interval of 100 perhaps? – Lasse V. Karlsen Jul 10 '20 at 08:11
  • How close do you need it to 100 ms? 100ms minus the time it takes to call the sensors? – TheGeneral Jul 10 '20 at 08:11
  • Task.Delay(100)? https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay?view=netcore-3.1 – Murray Foxcroft Jul 10 '20 at 08:11
  • How about `Thread.Sleep(100);`? From [here](https://stackoverflow.com/questions/9212966/how-can-i-put-a-delay-with-c) – funie200 Jul 10 '20 at 08:11
  • Well, I need the loop to take around 100 ms, but fetching the values from the sensor can take between 5 and 25 ms, this is never exactly the same – Foitn Jul 10 '20 at 08:12
  • 1
    `Stopwatch.GetTimestamp` and perhaps `TimeSpan.FromTicks` are your friends. See what time it is when you start the loop, do the expensive stuff, see what time it is now, sleep for `start + 100ms - now` – canton7 Jul 10 '20 at 08:12
  • What do you mean by "async" in this context? An async sleep is done using `await Task.Delay(sleepMillisecs)` – Matthew Watson Jul 10 '20 at 08:12
  • Do bear in mind that timers on Windows are horrendously inaccurate, and that's just the reality of a non-realtime operating system. You're lucky to be accurate to tens of ms, and there will be random delays which are way above 100ms – canton7 Jul 10 '20 at 08:13
  • use a [stopwatch](https://learn.microsoft.com/dotnet/api/system.diagnostics.stopwatch?view=netcore-3.1), do your measures, get the time elapsed and wait for `100 - sw.ElapsedMilliseconds` – Cid Jul 10 '20 at 08:13
  • 1
    Does this answer your question? [C# Timer for Millisecond Waits](https://stackoverflow.com/questions/9891879/c-sharp-timer-for-millisecond-waits) – janw Jul 10 '20 at 08:15
  • Guys, I do know that since I'm running windows, timing is not a good thing, I know that sleep of 100 ms is at least 100 ms. But I want to know how to perform this sort of action by running some kind of async sleep, if possible without uses of stopwatches. – Foitn Jul 10 '20 at 08:18
  • Why don't you want to use `Stopwatch`? Windows has some very high-accuracy timers, and `Stopwatch` is the API you use to access them (which is perhaps slightly unintuitive, but that's how it is) – canton7 Jul 10 '20 at 08:20
  • 1
    This answer might be helpful: [Run async method regularly with specified interval](https://stackoverflow.com/questions/30462079/run-async-method-regularly-with-specified-interval/62724908#62724908). For completing after 10 seconds you could include a `Stopwatch` in the logic, or use a `CancellationToken` that will be canceled after 10 seconds (method [`CancelAfter`](https://learn.microsoft.com/en-us/dotnet/api/system.threading.cancellationtokensource.cancelafter)). – Theodor Zoulias Jul 10 '20 at 08:32

1 Answers1

5

One way of doing it would be to create a Stopwatch and just wait for it to reach the next milestone after doing each operation.

The benefit of this way is that:

  • You will add the overhead of the loop into the mix, adjusting your delay to compensate
  • As such, it should have little to no "drifting" (where operations will get progressively "later" according to when they should've happened)

Sort of like this:

var sw = Stopwatch.StartNew();
long next = 100;
while (sw.ElapsedMilliseconds < 10000)
{
    // do your operation here
    long left = next - sw.ElapsedMilliseconds;
    if (left > 0)
        await Task.Delay((int)left);
    next += 100;
}

Due to the accuracy of things, your operations will around each 100 milliseconds, but they shouldn't drift.

If you're OK with tying up a thread for the duration of those 10 seconds, you could switch out the delay part with Thread.Sleep:

if (left > 0)
    Thread.Sleep((int)left);

It will run your operations closer to the 100 millisecond mark, but might still have a slight overhead due to the fact that Thread.Sleep has some overhead as well.

If you're OK with "burning CPU", you can do this instead:

var sw = Stopwatch.StartNew();
var next = 100;
while (sw.ElapsedMilliseconds < 10000)
{
    // do your operation here
    
    while (sw.ElapsedMilliseconds < next) ;
    next += 100;
}

This will run your operation even closer to the actual 100 milliseconds mark, as Task.Delay and Thread.Sleep will have overhead that will tend to delay just slightly longer than what you intended. It will, however, tie up a CPU core for the duration of those 10 seconds.

Lasse V. Karlsen
  • 380,855
  • 102
  • 628
  • 825
  • This looks quite promising! Of course the burning CPU can be less when placing an await Task.Delay(1) inside. – Foitn Jul 10 '20 at 08:20
  • 1
    If you're going to put an `await Task.Delay(1)` inside a while loop, you're better off with the first part of the answer, which uses a single `Task.Delay` – canton7 Jul 10 '20 at 08:21
  • 2
    Adding `await Task.Delay(1)` will give you the same "around 100 milliseconds" inaccuracy as the first part. Perhaps using `Thread.Sleep(...)` in those cases will have less overhead. You would then tie up a thread for 10 seconds, but not a whole CPU. – Lasse V. Karlsen Jul 10 '20 at 08:24