I have a situation where it makes more sense to me to have a delay between periodic actions wait for an amount of time in the real world to pass rather than waiting for the system clock to tick some number of times. This way I could, say, renew a lease being tracked on a different system/being timed out in real time after some amount of real time passes.
I suspected that Task.Delay
might already have this behavior, but I wanted to make sure, so I wrote a test program (see below). My discovery was that Task.Delay
behaves quite differently when the system is suspended and resumed. From observing its behavior, Task.Delay
acts as if it:
Is there a way to await
in such a way that I can run a task after some amount of real time passes so that if the system or process is resumed after the delay would have expired my continuation can be triggered? Right now, as a workaround, I’m just continuing whenever either Task.Delay
expires or SystemEvents.PowerModeChanged
fires Resume
. Is this the correct way to handle the situation? It seems odd to me to have to compose two APIs intended for different purposes this way and I was surprised to see that SystemEvents.PowerModeChanged
exists. Also, I fear that this API, being in the Microsoft.Win32
namespace, may not be portable.
using Microsoft.Win32;
using System;
using System.Threading.Tasks;
class Program
{
static int Main(string[] args) => new Program().Run(args).Result;
async Task<int> Run(string[] args)
{
SystemEvents.PowerModeChanged += (sender, e) => Console.WriteLine($"{e}: {e.Mode}");
var targetTimeSpan = TimeSpan.FromSeconds(20);
var start = DateTime.UtcNow;
var task = Task.Delay(targetTimeSpan);
var tickerTask = Tick(targetTimeSpan);
Console.WriteLine($"Started at {start}, waiting {targetTimeSpan}.");
await task;
var end = DateTime.UtcNow;
Console.WriteLine($"Ended at {end}, waited {end - start}.");
await tickerTask;
return 0;
}
async Task Tick(TimeSpan remaining)
{
while (remaining > TimeSpan.Zero)
{
Console.WriteLine($"tick: {DateTime.UtcNow}");
await Task.Delay(TimeSpan.FromSeconds(1));
remaining -= TimeSpan.FromSeconds(1);
}
}
}
In my program, I set task
to a Task.Delay(TimeSpan.FromSeconds(20))
. I then also print the current date once every second (plus a small amount of time) using a loop which runs 20 times (tickerTask
).
The output for a system suspend resume is:
tick: 2016-07-05 A.D. 14:02:34
Started at 2016-07-05 A.D. 14:02:34, waiting 00:00:20.
tick: 2016-07-05 A.D. 14:02:35
tick: 2016-07-05 A.D. 14:02:36
tick: 2016-07-05 A.D. 14:02:37
tick: 2016-07-05 A.D. 14:02:38
tick: 2016-07-05 A.D. 14:02:39
tick: 2016-07-05 A.D. 14:02:40
tick: 2016-07-05 A.D. 14:02:41
Microsoft.Win32.PowerModeChangedEventArgs: Suspend
tick: 2016-07-05 A.D. 14:02:42
tick: 2016-07-05 A.D. 14:02:44
tick: 2016-07-05 A.D. 14:03:03
Microsoft.Win32.PowerModeChangedEventArgs: Resume
tick: 2016-07-05 A.D. 14:03:05
tick: 2016-07-05 A.D. 14:03:06
tick: 2016-07-05 A.D. 14:03:08
tick: 2016-07-05 A.D. 14:03:09
tick: 2016-07-05 A.D. 14:03:10
tick: 2016-07-05 A.D. 14:03:11
tick: 2016-07-05 A.D. 14:03:12
Ended at 2016-07-05 A.D. 14:03:13, waited 00:00:38.8964427.
tick: 2016-07-05 A.D. 14:03:13
tick: 2016-07-05 A.D. 14:03:14
As you can see, I suspended my computer at 14:02:44 and resumed it at 14:03:03. Further, you can see that Task.Delay(TimeSpan.FromSeconds(20))
behaved roughly the same as looping 20 times over Task.Delay(TimeSpan.FromSeconds(1))
. The total wait time of 38.9 seconds is roughly 20 seconds plus the sleep time of 18 seconds (03:03 minus 02:44). I was hoping that the total wait time would be the time prior to resume plus the sleep time: 28 seconds or 10 (02:44 minus 02:34) plus 18 seconds (03:03 minus 02:44).
When I use Process Explorer to suspend and resume the process, the Task.Delay()
does faithfully complete after 20 seconds of real time. However, I am not certain that Process Explorer is actually suspending all of the threads of my process properly—maybe the message pump continues to run? Yet, the particular case of the process being suspended and resumed externally is both not really something most developers would try to support nor is it that different from normal process scheduling (which Task.Delay()
is expected to handle).
A delayed task starts as soon as it is eligible to run, and it finishes at a user-specified time. A started delayed-type task has a status of `In Progress` until it reaches its planned-for time, when it then changes its status to `Complete`.
Task. Delay() is asynchronous. It doesn't block the current thread. You can still do other operations within current thread.
A simple solution would be to write a method that periodically checks the current time and completes when difference from the start time reaches the desired amount:
public static Task RealTimeDelay(TimeSpan delay) =>
RealTimeDelay(delay, TimeSpan.FromMilliseconds(100));
public static async Task RealTimeDelay(TimeSpan delay, TimeSpan precision)
{
DateTime start = DateTime.UtcNow;
DateTime end = start + delay;
while (DateTime.UtcNow < end)
{
await Task.Delay(precision);
}
}
What precision
you should use depends on, well, the precision you require and the performance you need (though this likely isn't going to be a problem). If your delays are going to be in the range of seconds, then precision of hundreds of milliseconds sounds reasonable to me.
Note that this solution won't work correctly if the time on the computer changes (but DST transitions or other timezone changes are fine, since it's using UtcNow
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With