Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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());
    }
}
like image 557
derekhh Avatar asked Oct 08 '15 07:10

derekhh


People also ask

How to wait for task to complete in C#?

WaitAll() method in C# is used to wait for the completion of all the objects of the Task class. The Task class represents an asynchronous task in C#. We can start threads with the Task class and wait for the threads to finish with the Task. WaitAll() method in C#.

What is the difference between task run and task factory StartNew?

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!

What is task factory StartNew in C#?

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 use of task run in C#?

The Run method allows you to create and execute a task in a single method call and is a simpler alternative to the StartNew method. It creates a task with the following default values: Its cancellation token is CancellationToken.


2 Answers

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.

like image 124
Yuval Itzchakov Avatar answered Oct 05 '22 06:10

Yuval Itzchakov


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.

like image 32
Ufuk Hacıoğulları Avatar answered Oct 05 '22 06:10

Ufuk Hacıoğulları