Consider this code snippet:
Task[] tasks = new Task[4];
for (var i = 0; i < tasks.Length; ++i) {
tasks[i] = Task.Run(async () =>
{
await Task.Delay(4000);
});
}
for (var i = 0; i < tasks.Length; ++i)
await tasks[i];
Console.WriteLine("Done!");
This works as expected, taking 4.000 ms to execute. However, if I exchange Task.Run with Task.Factory.StartNew it takes only 0.006 ms !
Can anyone explain why?
Task. Run(action) internally uses the default TaskScheduler , which means it always offloads a task to the thread pool. StartNew(action) , on the other hand, uses the scheduler of the current thread which may not use thread pool at all!
StartNew(Action<Object>, Object, CancellationToken, TaskCreationOptions, TaskScheduler) Creates and starts a task for the specified action delegate, state, cancellation token, creation options and task scheduler.
request thread (ASP.NET thread) starts the GetAsync method and calls DoComplexCalculusAsync() asynchronously. Inside DoComplexCalculusAsync(), Task. Run uses another new thread from thread pool to do the heavy calculations in the background.
As you probably recall, await captures information about the current thread when used with Task. Run . It does that so execution can continue on the original thread when it is done processing on the other thread.
Can anyone explain why?
Put simply, StartNew does not understand async delegates.
So, when your delegate returns an incomplete task at its first await, StartNew sees the delegate exit and considers its work complete. This is why it returns a Task<Task> here. Task.Run has special logic to handle asynchronous delegates, automatically unwrapping the inner task.
This is only one of the pitfalls of using StartNew with asynchronous code; I cover this and the others in detail in my blog post StartNew is Dangerous.
Ok, I found the answer myself after reading this https://blogs.msdn.microsoft.com/pfxteam/2011/10/24/task-run-vs-task-factory-startnew/
By using the async keyword here, the compiler is going to map this delegate to be a
Func<Task<int>>: invoking the delegate will return theTask<int>to represent the eventual completion of this call. And since the delegate isFunc<Task<int>>,TResultisTask<int>, and thus the type of ‘t’ is going to beTask<Task<int>>, notTask<int>.
So this code works as expected:
Task[] tasks = new Task[4];
for (var i = 0; i < tasks.Length; ++i) {
tasks[i] = Task.Factory.StartNew(async () =>
{
await Task.Delay(4000);
});
}
for (var i = 0; i < tasks.Length; ++i)
await await (tasks[i] as Task<Task>);
Console.WriteLine("Done!");
Which can be implemented also using Unwrap:
Task[] tasks = new Task[4];
for (var i = 0; i < tasks.Length; ++i) {
tasks[i] = Task.Factory.StartNew(async () =>
{
await Task.Delay(4000);
}).Unwrap();
}
for (var i = 0; i < tasks.Length; ++i)
await tasks[i];
Console.WriteLine("Done!");
The answer is here
there is a difference in behavior between the two methods regarding : Task.Run(Action) by default does not allow child tasks started with the TaskCreationOptions.AttachedToParent option to attach to the current Task instance, whereas StartNew(Action) does
So, the Task.Run will wait while execution will finish and the Task.Factory.StartNew return a task immediately.
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