task . factory . startnew不要等待任务完成

本文关键字:任务 等待 factory startnew task | 更新日期: 2023-09-27 18:16:31

我发现,如果我使用实现,下面的代码实际上不会等待任务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());
    }
}

根据这篇MSDN文章,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 . factory . startnew不要等待任务完成

问题在于Task.Factory.StartNew不是"任务感知"。这意味着,从您的方法调用到StartNew的返回类型实际上是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确实工作,因为它是"任务感知"。它有一个接受Func<Task>的重载,它在内部为您调用Unwrap,只返回内部任务。

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

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

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

语句等待new Program().Foo()的结束

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

"问题"是,由于您在await上返回,因此调用Foo方法的任务已经完成,甚至在您点击命令行输出之前(然而,Foo方法返回的任务尚未完成)。

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

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

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

要有你想要的行为,你应该有一些像

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

但是,启动一个新任务只是为了等待另一个任务是没有多大意义的,所以你最好使用taskList.Add(new Program().Foo());