Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why Task.Factory.StartNew returns immediately while Task.Run does not?

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?

like image 303
FindOutIslamNow Avatar asked Jan 22 '17 07:01

FindOutIslamNow


People also ask

What is the difference between Task run and Task factory StartNew?

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!

What does Task factory StartNew do?

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.

Does Task run create a new thread?

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.

What does await Task Run do?

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.


3 Answers

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.

like image 124
Stephen Cleary Avatar answered Sep 29 '22 16:09

Stephen Cleary


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 the Task<int> to represent the eventual completion of this call. And since the delegate is Func<Task<int>>, TResult is Task<int>, and thus the type of ‘t’ is going to be Task<Task<int>>, not Task<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!");
like image 37
FindOutIslamNow Avatar answered Sep 29 '22 17:09

FindOutIslamNow


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.

like image 25
Vasyl Zv Avatar answered Sep 29 '22 17:09

Vasyl Zv