FindOutIslamNow
FindOutIslamNow

Reputation: 1236

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?

Upvotes: 10

Views: 7825

Answers (3)

Stephen Cleary
Stephen Cleary

Reputation: 456527

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.

Upvotes: 10

Vasyl Zvarydchuk
Vasyl Zvarydchuk

Reputation: 3839

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.

Upvotes: 3

FindOutIslamNow
FindOutIslamNow

Reputation: 1236

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!");

Upvotes: 8

Related Questions