I'm looking to add a module to one of my utility classes that functions as a countdown timer for tools that execute tasks after a certain amount of time. The code below is what I have working for PoC, but when I went to make this and check out existing examples, none really seemed this slim/simple without going all-out on lambdas, so I'm worried that I'm doing something wrong, or at the very least very inefficiently.
The time-range expected for the tool will be from 200 milliseconds to a few hours.
using System;
namespace CountdownTimer
{
class Program
{
static int Main(string[] args)
{
int aDelay = 2000;
CountDown(aDelay);
return 0;
}
static void CountDown(int delayInMilliseconds)
{
bool finishedCountdown = false;
DateTime initialTime = DateTime.UtcNow;
while (!finishedCountdown)
{
DateTime timeNow = DateTime.UtcNow;
TimeSpan timeDifference = timeNow - initialTime;
if (timeDifference.TotalMilliseconds >= delayInMilliseconds)
{
finishedCountdown = true;
}
}
}
}
}
I also have this alternative when I was futzing around with async, which I'm kind of new to in C#
using System;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace CountdownTimer
{
class Program
{
static int Main(string[] args)
{
var result = CountDownTimer(5000);
Console.ReadKey(true);
return 0;
}
async static Task CountDownTimer(int delay)
{
Task myDelay = AsyncDelay(delay);
await myDelay;
}
async static Task AsyncDelay(int delay)
{
await Task.Delay(TimeSpan.FromMilliseconds(delay));
Console.WriteLine("Done");
}
}
}
I think they're both pretty easy to understand, but my uncertainty with C#'s async for the second solution and the CPU cycles being used for the first worry me...
side note: Any version of .NET is available for use
-
3\$\begingroup\$ The first implementation is blocking (blocks the calling thread) and consumes 100% CPU. It should never be considered. \$\endgroup\$Oguz Ozgul– Oguz Ozgul2016年01月12日 06:20:16 +00:00Commented Jan 12, 2016 at 6:20
-
1\$\begingroup\$ I have rolled back the last edit. Please see what you may and may not do after receiving answers - if you would like the modified code peer reviewed, feel free to ask a follow-up question with the new code. \$\endgroup\$Mathieu Guindon– Mathieu Guindon2016年01月12日 19:06:59 +00:00Commented Jan 12, 2016 at 19:06
-
\$\begingroup\$ "...should not append your revised code to the question." Got it! Thanks, @Mat'sMug ! Any other CR posts you'd consider good to review not already covered in the help center? \$\endgroup\$kayleeFrye_onDeck– kayleeFrye_onDeck2016年01月12日 19:11:25 +00:00Commented Jan 12, 2016 at 19:11
-
1\$\begingroup\$ There are tons! You could start with this one perhaps, and then keep reading anything tagged with faq if you really want to read more :) \$\endgroup\$Mathieu Guindon– Mathieu Guindon2016年01月12日 19:12:30 +00:00Commented Jan 12, 2016 at 19:12
1 Answer 1
Without more context it's really hard to give you a particularly helpful answer but I can suggest a couple of things.
Your first option is about as bad as it gets, it will thrash CPU because as far as the CPU is concerned that while loop is important work - it doesn't know you're just calculating a time over and over again.
Your second option isn't bad but you should accept an action to run after the delay otherwise it won't be very flexible. You should also use TimeSpan
to represent an interval - an int
can mean many things, a TimeSpan
represents the information unambiguously.
For the sake of completeness, for this trivial example I think it's clearer without the async
and await
but that's just personal preference.
public static Task DoActionAfter(TimeSpan delay, Action action)
{
return Task.Delay(delay).ContinueWith(_ => action());
}
As you're returning a Task
your code can synchronously wait using .Wait()
(not great) or await
the result. If it doesn't need the result straight away it can just store the task and check its status later.
If you want to be able to cancel your action you should feed a CancellationToken
into the method (and the delay).
Update
To actually use the method:
using System;
using System.Threading.Tasks;
namespace CountdownTimer
{
class Program
{
public static void Main()
{
var delayInterval = TimeSpan.FromMilliseconds(5000);
var runningTask = DoActionAfter(
delayInterval,
() => Console.WriteLine("Done"));
// if you want your main method to wait for the action
// runningTask.Wait()
Console.ReadKey();
}
public static Task DoActionAfter(TimeSpan delay, Action action)
{
return Task.Delay(delay).ContinueWith(_ => action());
}
}
}
-
\$\begingroup\$ Hi RobH, I want to make sure I'm correctly understanding you. The method you shared consolidates the two methods from Solution 2 into a single method that processes a delay, then executes an Action? \$\endgroup\$kayleeFrye_onDeck– kayleeFrye_onDeck2016年01月12日 18:42:06 +00:00Commented Jan 12, 2016 at 18:42
-
\$\begingroup\$ @kayleeFrye_onDeck - That's almost right. It creates a Task that will complete after the specified delay (
Task.Delay
) and registers a continuation that runs when the delay is complete which simply executes theAction
. I updated my answer with an example. \$\endgroup\$RobH– RobH2016年01月12日 18:56:12 +00:00Commented Jan 12, 2016 at 18:56