Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Task.Delay doesn`t work in this situation

Tags:

I'm testing the async and I found this situation that I can't understand:

var watch = Stopwatch.StartNew();  var t1 = Task.Factory.StartNew(async () => {     await Task.Delay(2000);      return 2; });  var t2 = Task.Factory.StartNew(() => {     Task.Delay(1000);      return 1;  });  await Task.WhenAll(t1, t2);  var result = watch.ElapsedMilliseconds; 

I would like to understand why the result is always 0! Why is not 1000, 2000 or the sum of the two tasks 3000? Why doesn't Task.WhenAll wait for the completion of the tasks?

like image 272
MuriloKunze Avatar asked Dec 28 '12 15:12

MuriloKunze


People also ask

Does task delay block?

Task. Delay() is asynchronous. It doesn't block the current thread. You can still do other operations within current thread.

What is a delayed task?

Delay tasks can be used to stop the workflow for a specified duration. Delay tasks are always automated. The delay can be set to affect the planned or actual time of a task in a workflow. You can also set the date and time on which the next task in the workflow should be run.

Does task delay block the thread?

Delay(1000) doesn't block the thread, unlike Task. Delay(1000). Wait() would do, more details.

Which method is used for delaying the operation in particular intervals?

The Delay method is typically used to delay the operation of all or part of a task for a specified time interval.


1 Answers

Okay, so, the second one is the easy one, so let's handle that one.

For the second task, t2, you don't do anything with the result of Task.Delay(1000). You don't await it, you don't Wait it, etc. Given that the method is not async I supposed you meant for it to be a blocking wait. To do that you'd want to add Wait() to the end of the Delay call to make it a blocking wait, or just use Thread.Sleep().


For the first task, you're being bitten by the fact that you're using var. It's clearer what's happening when you don't use var:

Task<Task<int>> t1 = Task.Factory.StartNew(async () => {     await Task.Delay(2000);      return 2; }); 

You're returning a task of task of int, not just a Task of int. The outer task is "completed" as soon as the inner task finishes being started. When you use WhenAll you don't care about when the outer task finishes, you care about when the inner task finishes. There are a number of ways to handle this. One is to use Unwrap.

Task<int> t1 = Task.Factory.StartNew(async () => {     await Task.Delay(2000);      return 2; }).Unwrap(); 

Now you have your expected Task<int> and WhenAll will take at least 2000 milliseconds, as expected. You could also add in another await call to do the same thing:

Task<int> t1 = await Task.Factory.StartNew(async () => {     await Task.Delay(2000);      return 2; }); 

As mentioned by svick in a comment another option would be to just use Task.Run instead of StartNew. Task.Run has a special set of overloads for methods that take a Func<Task<T>> and return a Task<T> and automatically unwrap them for you:

Task<int> t1 = Task.Run(async () => {     await Task.Delay(2000);      return 2; }); 

For this reason it's preferable to use Task.Run as the default option when you're creating async lambdas as it will "handle" this issue for you, although it's best to be aware of it for the complex cases where you can't use Task.Run.


Finally we come to the option that you didn't do, which is what you probably should be actually doing in this case. Since Task.Delay already returns a Task, there's no need to put it in StartNew in the first place. Rather than creating a nested task and using Unwrap you can just not wrap it in the first place:

var t3 = Task.Delay(3000); await Task.WhenAll(t1, t2, t3); 

If you actually just want to wait for a fixed amount of time that's what you should be doing.

like image 63
Servy Avatar answered Oct 10 '22 21:10

Servy