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>>
,TResult
isTask<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