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

时间:2015-10-08 07:46:42

标签: c# async-await task-parallel-library

我发现以下代码实际上不会等待任务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());
    }
}

3 个答案:

答案 0 :(得分:12)

问题在于Task.Factory.StartNew不是“任务感知”的事实。意思是,方法调用StartNew的返回类型实际上是Task<Task>。这意味着您只是等待 outter任务完成,而不是内部任务

一个简单的解决方案是使用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确实有效,因为它是“任务感知”。 It has an overload taking a Func<Task>,内部为您调用Unwrap,仅返回内部任务。

答案 1 :(得分:2)

Task.Factory.StartNew默认情况下不会等待内部任务的完成。如果您创建一个带有返回值的变量,您将得到:

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

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

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

您发布的博客文章还解释了同步和异步代理之间的区别。

答案 2 :(得分:1)

您的代码会使用语句new Program().Foo()

等待Task.WaitAll(taskList.ToArray());的结尾

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

&#34;问题&#34;是因为你返回await,调用Foo方法完成的任务在那时完成,甚至在你命令行输出命令之前(任务返回< / em>然而,Foo方法并不完整。

你基本上有3&#34;时间轴&#34; (我不会说线程,因为我不确定HttpClient实际上是否启动了一个新线程,但是如果有人能够确认这一点,我会编辑帖子。)和2个任务:

  • 您的Main主题
  • 使用StartNew
  • 创建的任务
  • SendAsync任务。

您的Main主题正在等待StartNew创建的任务结束,但该任务等待SendAsync调用结束,因此不会等待命令行输出认为自己已完成。由于WaitAll中只考虑了那个而且SendAsync不是,所以它很早就会返回。

要获得所需的行为,您应该拥有类似

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

然而又一次,开始一个新任务只是为了等待另一个任务不会有意义,所以你最好选择taskList.Add(new Program().Foo());