Regarding Usage of Task.Start() , Task.Run() and Task.Factory.Startnew()

Regarding usage of Task.Start() , Task.Run() and Task.Factory.StartNew()

Task.Run is a shorthand for Task.Factory.StartNew with specific safe arguments:

Task.Factory.StartNew(
action,
CancellationToken.None,
TaskCreationOptions.DenyChildAttach,
TaskScheduler.Default);

It was added in .Net 4.5 to help with the increasingly frequent usage of async and offloading work to the ThreadPool.

Task.Factory.StartNew (added with TPL in .Net 4.0) is much more robust. You should only use it if Task.Run isn't enough, for example when you want to use TaskCreationOptions.LongRunning (though it's unnecessary when the delegate is async. More on that on my blog: LongRunning Is Useless For Task.Run With async-await). More on Task.Factory.StartNew in Task.Run vs Task.Factory.StartNew

Don't ever create a Task and call Start() unless you find an extremely good reason to do so. It should only be used if you have some part that needs to create tasks but not schedule them and another part that schedules without creating. That's almost never an appropriate solution and could be dangerous. More in "Task.Factory.StartNew" vs "new Task(...).Start"

In conclusion, mostly use Task.Run, use Task.Factory.StartNew if you must and never use Start.

What is the difference between Task.Run and Task.Factory.StartNew


Task.Run is actually implemented in terms of the same logic used for
Task.Factory.StartNew, just passing in some default parameters. When
you pass an Action to Task.Run:

Task.Run(someAction);

that’s exactly equivalent to:

Task.Factory.StartNew(someAction, 
CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

Read more here.

If you pass CancellationToken.None, TaskCreationOptions.DenyChildAttach and TaskScheduler.Default arguments for Task.Factory.StartNew parameters you should see the same result.

Task.Run vs Task.Factory.StartNew - Expected Deadlock is not happening

The method Task.Factory.StartNew when used with an async delegate returns a nested task: Task<Task>. You are assigning this nested task to a variable of type Task, which is allowed because the class Task<TResult> derives from the class Task. What happens then is that you lose the reference to the inner task, so you have no way of awaiting it. When you await this.backgroundTask you are awaiting the outer Task<Task>, but you can't get the result of the await operation, which is a Task, the inner Task, and await it. The inner task has become a fire-and-forget task.

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

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

Task.Factory.StartNew vs new Task

I found this great article by Stephen Toub, which explains that there is actually a performance penalty when using new Task(...).Start(), as the start method needs to use synchronization to make sure the task is only scheduled once.

His advice is to prefer using Task.Factory.StartNew for .net 4.0. For .net 4.5 Task.Run is the better option.

different behavior between Factory.StartNew and Task.Run?

The return type from the task factory is Task <Task>, the return type of Task.Run is just Task.

You need to unwrap the inner task with the factory so that your ConinueWith in the queue code is running the continuation on the inner task instead of the outer task.

static void Main(string[] args)
{
Console.WriteLine($"Starting test program (ManagedThreadId={Thread.CurrentThread.ManagedThreadId} IsThreadPoolThread={Thread.CurrentThread.IsThreadPoolThread})");

SerialTaskQueue co_pQueue = new SerialTaskQueue();

for (int i = 0; i < 2; i++)
{
var local = i;
co_pQueue.Enqueue(() => Task.Factory.StartNew(() => { return SimulateTaskSequence(local); }, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default).Unwrap());
}
}

Task.Run has a overload that accepts a Func<Task> that does this for you. If you declared the delegate in the Task.Run as a Func<object> you would see the same behavior from the Task.Run.

await Task.Factory.StartNew(() = versus Task.Start; await Task;

StartNew is just a short hand for creating and starting a task. If you want to do something to the Task instance before you start it, use the constructor. If you just want to create and start the task immediately, use the short hand.

Documentation for StartNew says:

Calling StartNew is functionally equivalent to creating a task by
using one of its constructors, and then calling the Task.Start method
to schedule the task for execution.



Related Topics



Leave a reply



Submit