-4

Take the simple example where the application calls the DoThis() function:

        public Task DoThis()
        {
            DoThisSub(); //Why is this being run?
            Task.Delay(100); //Yet..... Why is this ignored?
            Console.WriteLine("This is run immediately without delay");
            return Task.CompletedTask;
        }

        public Task DoThisSub()
        {
            Console.WriteLine("Yes I am being run!");
            return Task.CompletedTask;
        }

Why is DoThisSub being called but Task.Delay is not?

Saamer
  • 4,687
  • 1
  • 13
  • 55
user1034912
  • 2,153
  • 7
  • 38
  • 60
  • How to you it be ignored? – Epic Chen Jun 12 '21 at 00:53
  • 3
    `await` basically means "wait for the task to finish". Not using `await` means that you don't wait for the task to finish. What do you mean by `Task.Delay` is ignored? How do you know it's ignored? What did you expect to happen? `Task.Delay(100)` gives you a task that lasts 100ms, but you didn't wait for it. – Sweeper Jun 12 '21 at 00:53
  • 2
    First, `Task.Delay` isn't ignored. What you are doing is saying _start a 100 ms timer_. It returns a Task you could `await` or use to find out if it's completed. But you ignore it, so, the timer starts, it will eventually finish, but you don't care. That's what calling a task without awaiting it or doing anything else does, it just starts it up and forgets about it. It's often call _fire and forget_ – Flydog57 Jun 12 '21 at 00:57
  • Thanks Flydog.. but where is the timer run then? on a separate thread? I did not call Task.Run to create a new thread – user1034912 Jun 12 '21 at 01:12
  • Epic Chen.. it is ignored. I removed the console logs to make it simple. Let me add it back in – user1034912 Jun 12 '21 at 01:14
  • 1
    It doesn't necessarily run on a separate thread. Don't forget `Task != Thread`. The OS is likely able to signal the end of a timer without dedicating a thread to it. If you read from a port async, no thread is involved. When you call `ToListAsync` on the result of an EF query, it does an asynchronous read from the database but without dedicating a thread to the work. Lot's of things are naturally asynchronous in Windows – Flydog57 Jun 12 '21 at 01:51
  • 1
    You may find these interesting: [Task vs Thread differences](https://stackoverflow.com/questions/13429129/task-vs-thread-differences) and [There is no thread](https://blog.stephencleary.com/2013/11/there-is-no-thread.html). – Theodor Zoulias Jun 12 '21 at 01:58
  • Just a note to you, OP: do not post test questions (https://stackoverflow.com/q/67957237/2442804), they waste everybody's time, flagged your now deleted question for moderater intervention. – luk2302 Jun 13 '21 at 10:33

4 Answers4

1

The Task.Delay method creates a new task which will be complete after some time, therefore it is not being ignored. (Reference: https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.task.delay?view=net-5.0)

It is being called and another thread will probably run the delay (To know exactly which thread will run a Task depends on many factors), however as the calling thread is not awaiting the Task.Delay method it will immediately continue to process the next line.

If you want for the calling thread to await the Task.Delay method there are a couple of options:

  1. Make the DoThis method asyncronous and await the Task.Delay method.

    public async Task DoThis()
    {
        DoThisSub();
        await Task.Delay(100);
        Console.WriteLine("This will not be called immediately after the Task.Delay(100)");
        return Task.CompletedTask;
    }
    
  2. Call the Wait() method after the Task.Delay()

    public Task DoThis()
    {
        DoThisSub();
        Task.Delay(100).Wait();
        Console.WriteLine("This will not be called immediately after the Task.Delay(100)");
        return Task.CompletedTask;
    }
    

I highly recommend that you read microsoft async/await documentation. (Reference: https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/)

To address questions in comments:

why is Task.Delay run on a separate thread, where as DoThisSub() is not

The DoThisSub() method is just a regular method that returns a Task. Not all methods that return Task actually runs on new Threads. For this to happen the method needs to actually run code on a new Task as in the example below:

    public Task DoThisSub()
    {
        return Task.Run(() => 
        {
            Console.WriteLine("Yes I am being run on a new thread!");
        });
    }
Bruno Camba
  • 117
  • 6
  • Thanks all you say makes sense but why is Task.Delay run on a separate thread, where as DoThisSub() is not – user1034912 Jun 12 '21 at 03:01
  • The Task.Delay method is run on a separate thread because it is designed to work that way as the documentation says. If you want specifics you can look at the source code here: [link](https://github.com/microsoft/referencesource/blob/master/mscorlib/system/threading/Tasks/Task.cs) Looking at the source code you can see that actually a new Task is created for this method. The DoThisSub() method does not create a new Thread. It just is a method that returns a Task. Again if you truly want to understand this deeply read the microsoft documentation on async/await. – Bruno Camba Jun 12 '21 at 03:05
  • 1
    I am sorry if it sounded rude. It was not the intention. I just edited the answer to address this comment. Do you understand now? I really recommend that you read the documentation because a few years ago I was asking myself the same kind of questions and the only way I really understood what was going on was by reading it. – Bruno Camba Jun 12 '21 at 03:18
  • 1
    *"another thread is executing the delay"*, *"The Task.Delay method is run on a separate thread"* <== If this is true, then what will happen if I create 1,000,000 `Task.Delay(10000)` tasks and store them in a `List`? Are there going to be 1,000,000 separate threads running/executing the tasks? Am I going to see that many threads if I look my program in the Windows Task Manager? – Theodor Zoulias Jun 12 '21 at 03:28
  • 1
    Thank you Bruno, I wasn't offended at all. It was just interesting to know a lot of programmers (myself included) can survive all these years without properly understanding how async-await works under the hood. Your answer is the best. Thanks – user1034912 Jun 12 '21 at 03:45
  • 1
    Nice question, Theodor. I believe you will not be able to see that in Windows Task Manager because your computer probably does not have that many threads. The way that Tasks work is by actually delegating work to threads available in the thread pool. – Bruno Camba Jun 12 '21 at 03:46
  • 1
    Part of the idea of the paradigm is that we don’t HAVE to understand it all the way down to the registers. :) – Victor Wilson Jun 12 '21 at 03:52
  • 1
    Bruno the `ThreadPool` threads are still threads. They are not different threads than the threads created with the `Thread` constructor. If a `ThreadPool` thread was required in order to run/execute a `Task.Delay(10000)` task, and I have 1,000,000 tasks, and my `ThreadPool` has 4 threads, then only the first 4 tasks would complete in 10 sec, and the rest would drift. The tasks 5-8 would complete after 20 sec, the tasks 9-12 would complete after 30 sec etc. – Theodor Zoulias Jun 12 '21 at 14:31
1

Why is DoThisSub being called but Task.Delay is not?

They're both called. They both return task instances, which are both ignored.

There's nothing about "separate threads" going on here; all methods run on the same thread.

Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
0

It is not ignored! it is called on a separate thread, and since you are not awaiting result control moves immediately to next statement

Felix
  • 9,248
  • 10
  • 57
  • 89
  • Thanks. but Why is it being called on a separate thread? I didn't await if nor perform Task.Run in any of it. – user1034912 Jun 12 '21 at 01:10
  • Because that's what you asked it to do, by NOT waiting for it, it MUST run in a different thread. – Chris Schaller Jun 12 '21 at 01:19
  • 1
    Chris Schaller.. thanks but your statement is wrong... DoThisSub(); is NOT run on separate thread... it is run on the same thread! – user1034912 Jun 12 '21 at 02:57
  • 1
    What leads you to think this? Your test should try and print out the thread id inside the task. When you don't use `await` or `Task.Run` you can't create callback or at least control the marshalling and more importantly you can't capture the exceptions or results from the tasks. The task is left for the current scheduler context to manage, which _might_ end up being in the same thread, but _when_ is inderterminate – Chris Schaller Jun 12 '21 at 04:39
  • @ChrisSchaller `Task.Delay` doesn't run on another thread because it doesn't *run* anything at all. It just creates a task that will be completed after a specified time and *does nothing* for the duration. – Servy Jun 12 '21 at 23:21
  • What makes you think it's not being ignored? What do you think is doing anything with the `Task` in the code shown? – Servy Jun 12 '21 at 23:21
-1

There is no direct relationships between Task and Thread. Task-await-async is a mere simplified form of Asynchronous-task + EventHandler. (Related concepts: Events, Delegates, Lambda expressions).

For example, though we are quite casually using flat code as follows these days,

async {
    Debug.WriteLine("Before");
    await Task.Delay(100);

    Debug.WriteLine("After");
    :
    Debug.WriteLine("The End");
}

Once upon a time (maybe in .NET3.x or early .NET4.x era, see C# Version History), there were no await-async syntax. There was only AsynchronousDelayTaskWorker class or something which (1) takes event handler, (2) runs time-consuming task asynchronously and then (3) fires the event in the end. To use such apparatus, programmers had to write complex structured code as follows.

{
   Debug.WriteLine("Before");
   var worker = new AsynchronousDelayTaskWorker();
   worker.SetCompletedEventHandler(new CompletedEventHander(()=>
   {
       // Whether this handler is dispatched on the original thread or not
       // depends on worker's implementation. 

       Debug.WriteLine("After");
       :
       Debug.WriteLine("The End");
   }));
   worker.RunAsync();
}

But after task-async-await were introduced in .NET4.5, it became possible to write such a code in a flat and clean manner as follows.

async {

    Debug.WriteLine("Before");
    await Task.Delay(100);

    // Following codes are actually placed in event handler.
    Debug.WriteLine("After");
    :
    Debug.WriteLine("The End");
}

—— And C# programmers lived happily ever after.

Using task without await is nearly equivalent to running the legacy asynchronous task without registering event handler. The task runs asynchronously somewhere (in many cases in background thread). It's not possible to run another code after the task completes because it's unknown when the task finishes/finished, without event handler.

Since the task is mere object which doesn't interfere with subsequent codes, all the lines run straightforwardly.

{
    Debug.WriteLine("Before");

    // The following worker runs somewhere 
    // as the raw Task.Delay(100) does.
    var worker = new AsynchronousDelayTaskWorker();
    worker.RunAsync(); 

    // Following codes run immediately.
    Debug.WriteLine("After"); 
    :
    Debug.WriteLine("The End");
}
ardget
  • 2,561
  • 1
  • 5
  • 4