我发现以下代码实际上不会等待任务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());
}
}
答案 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
您的Main
主题正在等待StartNew
创建的任务结束,但该任务不等待SendAsync
调用结束,因此不会等待命令行输出认为自己已完成。由于WaitAll
中只考虑了那个而且SendAsync不是,所以它很早就会返回。
要获得所需的行为,您应该拥有类似
的行为taskList.Add(Task.Factory.StartNew(() => new Program().Foo().Wait()));
然而又一次,开始一个新任务只是为了等待另一个任务不会有意义,所以你最好选择taskList.Add(new Program().Foo());