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");
}
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.
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!
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.
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.
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:
Task.Factory.StartNew
spawns a task that calls MethodThatReturnsTask
, let's call this task "Task A"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.
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.
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.
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