Task.Factory.StartNew不会等待任务完成?

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

我发现如果我使用实现,下面的代码实际上不会等待任务 client.SendAsync():

taskList.Add(Task.Factory.StartNew(() => new Program().Foo()));

如果我将其从 Task.Factory.StartNew() 更改为 new Program().Foo()Task.Run(() => new Program.Foo(),它将正确输出一些信息。两者有什么区别?

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());
    }
}

基于this MSDN article,似乎Task.Run(someAction);等同于Task.Factory.StartNew(someAction, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);

但是即使我把代码改成这样,它也不会输出任何东西。为什么?

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());
    }
}

您的代码使用语句 Task.WaitAll(taskList.ToArray());

等待 new Program().Foo() 结束

然而,Foo 在 returning 之前不会等待 client.SendAsync(requestMessage) 结束,因为它有一个 await。 await 将使方法 return 成为 Task 对象并允许线程返回到下一行。

"problem"是因为你return在你的await上,调用Foo方法的任务在那个时候就已经完成了,甚至比你还早在命令行上点击输出(然而,通过 Foo 方法完成的任务 returned 尚未完成)。

你基本上有 3 个 "timelines"(我不会说线程,因为我不确定 HttpClient 是否真的启动了一个新线程,但如果有人可以确认这一点,我将编辑 post.) 和 2 个任务:

  • 你的 Main 线程
  • 使用StartNew
  • 创建的任务
  • SendAsync 任务。

您的 Main 线程正在等待 StartNew 创建的任务结束,但该任务 没有 等待 SendAsync 调用,因此不会等待命令行输出认为自己已完成。由于在 WaitAll 中只考虑了那个,而 SendAsync 没有,所以 return 这么早是正常的。

要有你想要的行为,你应该有类似的东西

taskList.Add(Task.Factory.StartNew(() => new Program().Foo().Wait()));

但话又说回来,开始一个新任务只是为了等待另一个任务没有多大意义,所以你最好选择 taskList.Add(new Program().Foo());

问题在于 Task.Factory.StartNew 不是 "task aware"。意思是,您对 StartNew 的方法调用中的 return 类型实际上是 Task<Task>。这意味着您只是在等待 外部任务 完成,而不是 内部任务

一个简单的解决方案是使用 TaskExtensions.Unwrap() 方法:

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 确实有效,因为它是 "task aware"。 It has an overload taking a Func<Task>,它在内部为您调用 Unwrap,return 仅执行内部任务。

Task.Factory.StartNew 默认不等待内部任务完成。如果您使用 return 值创建一个变量,您将得到:

Task<Task> task = Task.Factory.StartNew(() => new Program().Foo());

这意味着一旦内部任务命中 await 语句,委托就会 returns。您需要调用 UnWrap 方法来强制异步执行:

Task task = Task.Factory.StartNew(() => new Program().Foo()).Unwrap();
taskList.Add(task);

博客 post 您 posted 也解释了同步委托和异步委托之间的区别。