Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using await Task.Factory.StartNew on a method will return immediately

I'm running the following code (C#7.1 Console App), and I can't really understand why the difference in behavior.

If I await a regular async method call, or a Task.Run - it works as expected (i.e. the app doesn't return immediately). But if I use Task.Factory.StartNew - it will return immediately without the code actually running.

Strangely enough - if I use StartNew but inside the method remove the await, it will not return immediately...

Problem: This returns immediately:

static async Task Main(string[] args)
{
    await Task.Factory.StartNew(DisplayCurrentInfo);
}

static async Task DisplayCurrentInfo()
{
    await WaitAndApologize();
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Thread.Sleep(3000);
}

i.e. - I won't get to see anything printed out to the console, and the console will already be shut down.

No problem: this doesn’t return immediately:

static async Task Main(string[] args)
{
    await DisplayCurrentInfo(); // or await Task.Run(DisplayCurrentInfo);
}

static async Task DisplayCurrentInfo()
{
    await WaitAndApologize();
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Thread.Sleep(3000);
}

Strange: this also doesn't return immediately:

static async Task Main(string[] args)
{
    await Task.Factory.StartNew(DisplayCurrentInfo); 
}

static async Task DisplayCurrentInfo()
{
    WaitAndApologize();
    Console.WriteLine($"The current time is {DateTime.Now.TimeOfDay:t}");
    Thread.Sleep(3000);
}

WaitAndApologize:

static async Task WaitAndApologize()
{
    // Task.Delay is a placeholder for actual work.  
    await Task.Delay(2000);
    // Task.Delay delays the following line by two seconds.  
    Console.WriteLine("\nSorry for the delay. . . .\n");
}
like image 537
Maverick Meerkat Avatar asked Jun 22 '18 07:06

Maverick Meerkat


People also ask

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.

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!

Does await halt execution?

The await expression is usually used to unwrap promises by passing a Promise as the expression . This causes async function execution to pause until the promise is settled (that is, fulfilled or rejected), and to resume execution of the async function after fulfillment.

What is the difference between task run and async await?

An await expression in an async method doesn't block the current thread while the awaited task is running. Instead, the expression signs up the rest of the method as a continuation and returns control to the caller of the async method. The async and await keywords don't cause additional threads to be created.


3 Answers

If you use Task.Factory.StartNew(MethodThatReturnsTask) you get back a Task<Task<T>> or Task<Task> depending on whether the method is returning a generic task or not.

The end result is that you have 2 tasks:

  1. Task.Factory.StartNew spawns a task that calls MethodThatReturnsTask, let's call this task "Task A"
  2. MethodThatReturnsTask in your case returns a Task, let's call this "Task B", this means that an overload of StartNew that handles this is used and the end result is that you get back a Task A that wraps Task B.

To "correctly" await these tasks needs 2 awaits, not 1. Your single await simply awaits Task A, which means that when it returns, Task B is still executing pending completion.

To naively answer your question, use 2 awaits:

await await Task.Factory.StartNew(DisplayCurrentInfo);

However, it is questionable why you need to spawn a task just to kick off another async method. Instead you're much better off using the second syntax, where you simply await the method:

await DisplayCurrentInfo();

Opinion follows: In general, once you've started writing async code, using Task.Factory.StartNew or any of its sibling methods should be reserved for when you need to spawn a thread (or something similar) to call something that isn't async in parallel with something else. If you're not requiring this particular pattern, it's best to not use it.

like image 126
Lasse V. Karlsen Avatar answered Sep 22 '22 04:09

Lasse V. Karlsen


When you call Task.Factory.StartNew(DisplayCurrentInfo); you are using the signature:

public Task<TResult> StartNew<TResult>(Func<TResult> function)

Now since you are passing a method with the signature Task DisplayCurrentInfo() then the TResult type is Task.

In other words you are returning a Task<Task> from Task.Factory.StartNew(DisplayCurrentInfo) and you are then awaiting the outer task which returns Task - and you're not awaiting that. Hence it completes immediately.

like image 26
Enigmativity Avatar answered Sep 22 '22 04:09

Enigmativity


await Task.Factory.StartNew(DisplayCurrentInfo);

The first example does return imediatly, because you are creating a new Task and awaiting it, which will call another task but that is not waited. Think about a psudo code like below inorder to keep awaited.

await Task.Factory.StartNew( await DisplayCurrentInfo); //Imaginary code to understand

In the third example WaitAndApologize is not awaited, but its getting blocked by thread sleep (Full Thread is blocked).

With only the help of code we can't say that the Task factory has created a new thread or just running on the existing thread. If its running in the same thread, the whole thread is getting blocked, so you are getting a feeling that the code is awaiting at await Task.Factory.StartNew(DisplayCurrentInfo); , but atually its not happening.

If its running on a differnet thread, you will not get the same result as above.

like image 26
VibeeshanRC Avatar answered Sep 19 '22 04:09

VibeeshanRC