5
\$\begingroup\$

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

\$\endgroup\$
  • 3
    \$\begingroup\$ The first implementation is blocking (blocks the calling thread) and consumes 100% CPU. It should never be considered. \$\endgroup\$ – Oguz Ozgul Jan 12 '16 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 Jan 12 '16 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 Jan 12 '16 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 Jan 12 '16 at 19:12
4
\$\begingroup\$

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());
        }
    }
}
\$\endgroup\$
  • \$\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 Jan 12 '16 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 the Action. I updated my answer with an example. \$\endgroup\$ – RobH Jan 12 '16 at 18:56

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.