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 也解释了同步委托和异步委托之间的区别。
我发现如果我使用实现,下面的代码实际上不会等待任务 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 也解释了同步委托和异步委托之间的区别。