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?
Task. Delay() is asynchronous. It doesn't block the current thread. You can still do other operations within current thread.
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.
Delay(1000) doesn't block the thread, unlike Task. Delay(1000). Wait() would do, more details.
The Delay method is typically used to delay the operation of all or part of a task for a specified time interval.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With