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
不是"任务感知"。这意味着,从您的方法调用到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());