Task.Factory.StartNew won't wait for task completion?
Solution 1
The problem lays in the fact that Task.Factory.StartNew
isn't "task aware". Meaning, the return type from your method call to StartNew
is actually a Task<Task>
. This means that you're only waiting on the outter task to complete, not the inner one.
A simple solution to that would be to use the TaskExtensions.Unwrap()
method:
private static void Main(string[] args)
{
var taskList = new List<Task>();
taskList.Add(Task.Factory.StartNew(() => new Program().Foo()).Unwrap());
Task.WaitAll(taskList.ToArray());
}
Task.Run
does work because it is "task aware". It has an overload taking a Func<Task>
, which internally calls Unwrap
for you, returning only the inner task.
Solution 2
Task.Factory.StartNew
doesn't await the completion of the inner task by default. If you create a variable with the return value, you will get this:
Task<Task> task = Task.Factory.StartNew(() => new Program().Foo());
This means that the delegate returns as soon as the inner task hits the await statement. You need to call UnWrap
method to force the async execution:
Task task = Task.Factory.StartNew(() => new Program().Foo()).Unwrap();
taskList.Add(task);
Blog post you posted also explains this difference between synchronous and asynchronous delegates.
Solution 3
Your code waits for the end of new Program().Foo()
with the statement Task.WaitAll(taskList.ToArray());
However, Foo
doesn't wait for the end of client.SendAsync(requestMessage)
before returning, since it has an await
. The await will let the method return the Task
object and allow the thread to come back on the next line.
The "problem" is that since you return on your await
, the task that calls the Foo
method is complete is complete at that time, even before you hit the output on the command line (The Task returned by the Foo
method however, is not complete).
You basically have 3 "timelines" (I won't say thread because I'm not sure the HttpClient actually starts a new thread, but if someone can confirm this, I'll edit the post.) and 2 tasks:
- your
Main
thread - the task created with
StartNew
- The the SendAsync task.
Your Main
thread is waiting for the end of the Task created by StartNew
, but that Task does not wait for the end of the SendAsync
call, and therefore doesn't wait the command line output to consider itself as completed. Since only that one is considered in the WaitAll
and the SendAsync isn't, it is normal that it returns that early.
To have the behaviour you want, you should have something like
taskList.Add(Task.Factory.StartNew(() => new Program().Foo().Wait()));
but then again, starting a new Task just to wait for another one wouldn't make much sense, so you'd better go with taskList.Add(new Program().Foo());
derekhh
Currently I am a software engineer at Snap Inc. Previously I've been working at Microsoft Bing for almost four years. During my years at Microsoft I was the primary back-end algorithm developer or tech lead for many features related to natural language processing and machine learning. I've designed and implemented many core algorithms that powered Bing's conversational experience, question answering and entity carousel. Prior to joining Microsoft, I've obtained my Ph.D. from the Hong Kong University of Science and Technology. My research was primarily on the theme of sensor-based human activity recognition. Throughout my PhD years, I've published around 20 papers in top conferences and journals. I was also a winner of the Microsoft Research Fellowship in the year 2009. I also enjoy competitive programming a lot. I was a regular contestant in programming contests like acm/icpc, Google Code Jam and TopCoder Open. I've also won awards and top prizes from these competitions. Google Scholar Page: https://scholar.google.com/citations?user=Ks81aO0AAAAJ&hl=en Specialties: machine learning, data mining, algorithms, programming
Updated on June 05, 2022Comments
-
derekhh almost 2 years
I've found that the following code won't actually wait for the task client.SendAsync() if I use the implementation:
taskList.Add(Task.Factory.StartNew(() => new Program().Foo()));
If I change it from
Task.Factory.StartNew()
to justnew Program().Foo()
orTask.Run(() => new Program.Foo()
it will correctly output some information. What are the differences between the two?internal class Program { private async Task Foo() { while (true) { var client = new HttpClient(); var requestMessage = new HttpRequestMessage(HttpMethod.Head, "http://www.google.com"); HttpResponseMessage response = await client.SendAsync(requestMessage); Console.WriteLine(response.RequestMessage.RequestUri.ToString()); } } private static void Main(string[] args) { var taskList = new List<Task>(); // This won't output anything. taskList.Add(Task.Factory.StartNew(() => new Program().Foo())); // This will. taskList.Add(Task.Run(() => new Program().Foo())); // So does this. taskList.Add(new Program().Foo()); Task.WaitAll(taskList.ToArray()); } }
Based on this MSDN article, it seems
Task.Run(someAction);
is equivalent toTask.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
But even when I change the code to this, it won't output anything. Why?
internal class Program { private async Task Foo() { while (true) { var client = new HttpClient(); var requestMessage = new HttpRequestMessage(HttpMethod.Head, "http://www.google.com"); HttpResponseMessage response = await client.SendAsync(requestMessage); Console.WriteLine(response.RequestMessage.RequestUri.ToString()); } } private static void Main(string[] args) { var taskList = new List<Task>(); taskList.Add(Task.Factory.StartNew(() => new Program().Foo(), CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default)); //taskList.Add(Task.Run(() => new Program().Foo())); //taskList.Add(new Program().Foo()); Task.WaitAll(taskList.ToArray()); } }
-
Arghya C over 8 yearsAnswer of the question in subject line - No, unless you ask for.
-
Larry over 8 yearsHave a look to the async/await pattern. This will broad your horizon around this issue.
-
Stephen Cleary over 8 years@derekhh: Read your whole reference. The part you're quoting is prefixed with When you pass an Action to Task.Run, which your code is not doing. Further down the blog post he explains how
Task.Run
behaves differently with asynchronous delegates.
-
-
vighnesh153 over 4 yearsfor a single task:
var task = Task.Factory.StartNew(something);
task.Wait();