2

I have a Windows Service which starts a task on start up

This task which has a while loop and after performing one iteration it go to sleep for 5 minutes.

When I stop service, the task is cancelled first and later some other operations gets performed

if the task is in sleep, it get cancelled only when it wakes up , i want it to be cancelled even if it is sleeping and don't want to wait for waking it up.

following is the code

Task controllerTask = Task.Factory.StartNew(() =>
{
    var interval = 300;
    while(true)
    {
        if (cancellationToken.IsCancellationRequested) 
            break;
        Thread.Sleep(interval * 1000);
        if (cancellationToken.IsCancellationRequested) 
            break;
        //SOME WORK HERE
    }
}, cancellationToken);

Is there any way?

EDIT: I am not able to use Task.Delay , I can't find it in System.Threading.Tasks.Task namespace , because I am using .Net Framework 4.0 not 4.5

Is there any other better solution that works with 4.0.

weston
  • 54,145
  • 21
  • 145
  • 203
Imran Rizvi
  • 7,331
  • 11
  • 57
  • 101

4 Answers4

11

Use Task.Delay instead of Thread.Sleep. It takes a CancellationToken parameter so you can abort it before the end of the delay.

If you're using async code, you could write it like this:

await Task.Delay(duration, cancellationToken);

If it's synchronous code, you can just wait the task:

Task.Delay(duration, cancellationToken).Wait();
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • 1
    This also has the advantage that it doesn't tie up a thread pool thread for 5 minutes (in the first async case.) – Dan Bryant Jul 11 '13 at 15:41
  • @DanBryant Are you saying there that `await` does not tie it up, and `.Wait()` does? Or just that the OPs one tied it up. – weston Jul 11 '13 at 15:55
  • @Thomas can you please write solution on my code, where to put this line? in place of Thread.Sleep? – Imran Rizvi Jul 11 '13 at 16:00
  • @weston, `Wait` will block the calling thread (just like Sleep), whereas `await` registers a continuation and allows the current thread to continue (in most asynchronous coding cases, releasing the thread back to the pool.) The equivalent using TPL without async/await would require splitting up the OP's method into separate parts or rewriting it as a state machine in a helper class with the state number so that the method can be registered as its own continuation (basically what the compiler does when you use async/await.) – Dan Bryant Jul 11 '13 at 16:03
  • @DanBryant Cool, thanks, I have tried this out in my [answer](http://stackoverflow.com/a/17598298/360211). Did I do it right? – weston Jul 11 '13 at 16:08
  • I am not able to use Task.Delay , I can't find it in System.Threading.Tasks.Task namespace , because I am using .Net Framework 4.0 not 4.5 – Imran Rizvi Jul 11 '13 at 16:19
  • @ImranRizvi, not a problem, you can recreate the feature in .NET 4: http://stackoverflow.com/a/15342256/98713. You won't be able to use `await`, though... – Thomas Levesque Jul 11 '13 at 16:22
  • The implementation I mentioned doesn't take a cancellationToken, but since you will have to use `Wait` anyway, you can do this: `Delay(duration).Wait(cancellationToken)` – Thomas Levesque Jul 11 '13 at 16:25
  • 1
    @ImranRizvi, @ThomasLevesque: You can get `async` support for .NET 4 from the `Microsoft.Bcl.Async` NuGet library. It includes a `TaskEx.Delay` method. – Stephen Cleary Jul 11 '13 at 19:05
  • 1
    @StephenCleary, yes, but you still need VS2012 (or at least the C# 5 compiler) – Thomas Levesque Jul 11 '13 at 20:20
2

Inspired by the other answers, simple example of using await for this problem:

public static class TaskExtension
{
    /// <summary>
    /// Call to highlight fact that you do not want to wait on this task.
    ///
    /// This nicely removes resharper warnings without need for comments.
    /// </summary>
    /// <param name="task"></param>
    public static void FireAndForget(this Task task)
    {
    }
}

internal class Program
{
    private static void Main(string[] args)
    {
        var cancellationToken = new CancellationTokenSource();
        TaskCode(cancellationToken.Token).FireAndForget();
        Console.ReadLine();
        cancellationToken.Cancel();
        Console.WriteLine("End");
        Console.ReadLine();
    }

    private static async Task TaskCode(CancellationToken cancellationToken)
    {
        while (!cancellationToken.IsCancellationRequested)
        {
            var interval = TimeSpan.FromSeconds(1);
            await Task.Delay(interval, cancellationToken);

            //SOME WORK HERE
            Console.WriteLine("Tick");
        }
    }
}
weston
  • 54,145
  • 21
  • 145
  • 203
  • I would recommend having TaskCode return Task rather than void. A void return is really only intended for backwards compatibility so that event handler delegates can be made asynchronous without changing their return value. It's also a good idea to allow the TaskCanceledException to propagate rather than changing it into an empty `return`, as that way the final Task will correctly have a Cancelled state. – Dan Bryant Jul 11 '13 at 16:12
  • Actually, you don't need to use Task.StartNew, because TaskCode will return to the caller when the first `await` is encountered. It will then resume execution at the end of the `Delay`. BTW, you shouldn't pass the `CancellationTokenSource`, just the `CancellationToken`. – Thomas Levesque Jul 11 '13 at 16:15
  • Thanks for your answer @weston ,but I am not able to use Task.Delay , I can't find it in System.Threading.Tasks.Task namespace , because I am using .Net Framework 4.0 not 4.5 – Imran Rizvi Jul 11 '13 at 16:19
  • @DanBryant Thanks, I was catching the exception because it was `void` return type, so fixing the return type removed the need for that. – weston Jul 12 '13 at 08:06
  • @ThomasLevesque Like that? Note I get a resharper error warning me I am not awaiting the result of `TaskCode`, but I can ignore that with comment if there's no better way. – weston Jul 12 '13 at 08:18
  • @weston, if you know that you will never need to await the result of TaskCode, you can declare it as `async void` instead of `async Task`. If you want to keep the `async Task` signature, you can create a dummy `FireAndForget` extension method that does nothing but makes it clear that you didn't just forget to await the task. – Thomas Levesque Jul 12 '13 at 08:56
  • @ThomasLevesque I found that I need to keep the `async Task` because the `await Task.Delay` throws `TaskCanceledException` on cancellation and this cannot propagate or something, basically the example doesn't work without it. But the extension method is a nice idea, so have gone with that. – weston Jul 12 '13 at 09:35
2

This is one blocking solution you can use in C# 4.0, VS2010.

cancellationToken.WaitHandle.WaitOne(TimeSpan.FromMinutes(5));

It will unblock when you cancel the token source or on timeout which is your desired sleep interval.

YK1
  • 7,327
  • 1
  • 21
  • 28
0

I've broken long sleep into multiple small sleeps, following is the modified code:

Task controllerTask = Task.Factory.StartNew(() =>
{
 while(true)
 {
     if (cancellationToken.IsCancellationRequested) break;
     var sleepTime = 10;
     if (interval < sleepTime)
         interval = sleepTime;

     var iteration = (interval / sleepTime);
     if ((interval % sleepTime) > 0)
         iteration++;
     bool cancel = false;
     for (int i = 0; i < iteration; i++)
     {
         Thread.Sleep(sleepTime * 1000);
         if (cancellationToken.IsCancellationRequested) { cancel = true; break; };
     }
     if (cancel) break;

     //SOME WORK HERE
 }
}
Imran Rizvi
  • 7,331
  • 11
  • 57
  • 101