32

Task.Delay() not behaving as expected or rather I'm not understanding what it is supposed to do. I'm trying to get my head around Tasks in C# and how to replace Threads in my implementation.

What I'm trying to do is something like this:

  • While true
    • print a line
    • wait for a second,
    • If condition is satisfied, exit loop, otherwise keep looping

I've got that implemented with Threads fine, but all cool kids say I should be using Task, and don't touch Thread.

So for the code I have this (Ignore [Test] - this just a convenient way to try out things)

    [Test]
    public void ChattyTask()
    {
        var chattyTask = new Task(ChattyWriter);
        chattyTask.Start();
        chattyTask.Wait();
    }

    public void ChattyWriter()
    {
        int count = 0;
        while (true)
        {
            var message = String.Format("Chatty Writer number {0}", count);
            Trace.WriteLine(message);
            count++;
            Task.Delay(1000);

            if (count >= 20)
            {
                break;
            }
        }
    }

When I run this, the test finishes in milliseconds, not in 20 seconds as I would expect. If I replace Task.Delay() with Thread.Sleep(), everything works as expected and I get the printout once a second. I have tried adding async and await into ChattyWriter(), but not only it did not add a 1 second delay, it only printed only one line instead of 20.

What am I doing wrong?

Probably It'll help to describe what I'm doing: my project works with external API (RESTful) and after I request some tasks to happen, I need to poll the API to check if the task has been completed. External tasks can be long running: 1-15 minutes. So I need some delay between checking for completion. And there could be many different concurrent processes executed with multiple external tasks. I understand, that if I use Thread.Sleep() when polling, other processes on the same Thread will be blocked for no good reason.

Cristian Lupascu
  • 39,078
  • 16
  • 100
  • 137
trailmax
  • 34,305
  • 22
  • 140
  • 234

3 Answers3

58

Task.Delay returns a Task object on which you have to wait. Otherwise the following code will be executed immediately. Therfore you have to declare your method as async. Then you can await Task.Delay

public async Task ChattyWriter()
{
    int count = 0;
    while (true)
    {
        var message = String.Format("Chatty Writer number {0}", count);
        Trace.WriteLine(message);
        count++;
        await Task.Delay(1000);
           ...
    }
}

You have to interrupt your calling thread somehow. The unit test would terminate and the background thread as well. But if you call this method from the UI, the UI will not get blocked.

By calling Wait on an asynchronous method you will end up with a deadlock. See here for more information.

Community
  • 1
  • 1
slfan
  • 8,950
  • 115
  • 65
  • 78
  • I did try this, and it gave me only first line printed out and completed right after that. – trailmax Sep 07 '13 at 11:32
  • 2
    That's probably because your main thread (the test routine) can go on running and you don't see the rest, because the program terminates. Would you start the thread from a UI button, the UI would still be responsive. – slfan Sep 07 '13 at 11:36
  • You are absolutely correct on this one! I've placed `Thread.Sleep(20000)` in the test routine and got what was expecting. – trailmax Sep 07 '13 at 11:47
30

Task.Delay(1000); creates an async task that will complete after a second, but the execution of the current method will continue in parallel.

In order to wait for the task to complete first, you could replace

Task.Delay(1000);

with

Task.Delay(1000).Wait();
Cristian Lupascu
  • 39,078
  • 16
  • 100
  • 137
  • 3
    @trailmax But this creates a new task that does nothing for one second. A much better approach is to simply call Thread.Sleep(1000). – Alberto Sep 07 '13 at 11:37
  • @trailmax while my post explains why `Task.Delay` wasn't working, please consider using the approach described in [slfan's answer](http://stackoverflow.com/a/18672852/390819), which is more efficient. – Cristian Lupascu Sep 07 '13 at 11:43
  • is there a principal difference between the approaches? I always through that `async await` is just a syntactic sugar for compiler? – trailmax Sep 07 '13 at 11:45
  • @trailmax the difference is that `Task.Wait()` is synchronous. In your case it doesn't make much difference, since you're anyway calling `Wait()` on the containing task (`chattyTask`). So, consider changing that to be async as well. – Cristian Lupascu Sep 07 '13 at 12:01
  • 1
    @Alberto If I have more than one Task running on the same thread (that is possible according to MSDN), and call `Thread.Sleep()`, that would block other tasks. Please correct me if I'm wrong. – trailmax Sep 07 '13 at 12:06
  • 2
    Task.Delay has an advantage that it is cancellable when passing a CancellationToken. – Miha Markic Jun 25 '15 at 12:08
2

Task.Delay() actually creates a task, so you just need to wait for it.

Try this

        public static void ChattyWriter()
    {
        int count = 0;
        while (true)
        {
            var message = String.Format("Chatty Writer number {0}", count);
            Console.WriteLine(message);
            count++;
            var t = Task.Delay(1000);
            t.Wait();

            if (count >= 20)
            {
                break;
            }
        }
    }
Pandacoon
  • 35
  • 3
  • 3
    The primary reason why Task.Delay returns a Task is, that you can call await on it. Your answer is the same as @w0lf's – slfan Sep 07 '13 at 11:42