derekhh derekhh - 1 month ago 12
C# Question

Task.Factory.StartNew won't wait for task completion?

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 just
new Program().Foo()
or
Task.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 to
Task.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());
}
}

Answer

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.