为什么WebClient.DownloadStringTaskAsync()会阻塞? – 新的异步API /语法/ CTP

出于某种原因,在下面的程序启动后会暂停。 我相信WebClient().DownloadStringTaskAsync()是原因。

 class Program { static void Main(string[] args) { AsyncReturnTask(); for (int i = 0; i < 15; i++) { Console.WriteLine(i); Thread.Sleep(100); } } public static async void AsyncReturnTask() { var result = await DownloadAndReturnTaskStringAsync(); Console.WriteLine(result); } private static async Task DownloadAndReturnTaskStringAsync() { return await new WebClient().DownloadStringTaskAsync(new Uri("http://www.weather.gov")); } } 

据我所知,我的程序应该立即从0到15开始计数。 难道我做错了什么?

我在原始的Netflix下载示例(使用CTP获得)时遇到了同样的问题 – 按下搜索按钮后,UI首先冻结 – 一段时间后,它在加载下一部电影时响应。 我相信它并没有冻结Anders Hejlsberg在PDC 2010上的演讲。

还有一件事。 而不是

 return await new WebClient().DownloadStringTaskAsync(new Uri("http://www.weather.gov")); 

我用自己的方法:

 return await ReturnOrdinaryTask(); 

这是:

 public static Task ReturnOrdinaryTask() { var t = Task.Factory.StartNew(() => { for (int i = 0; i < 10; i++) { Console.WriteLine("------------- " + i.ToString()); Thread.Sleep(100); } return "some text"; }); return t; } 

它应该工作。 我的意思是它不加载任何东西,但它立即启动,并不会阻止主线程,同时做它的工作。

编辑

好吧,我现在相信的是: WebClient.DownloadStringTaskAsync函数被搞砸了。 它应该在没有初始阻塞期的情况下工作,如下所示:

  static void Main(string[] args) { WebClient cli = new WebClient(); Task.Factory.StartNew(() => { cli.DownloadStringCompleted += (sender, e) => Console.WriteLine(e.Result); cli.DownloadStringAsync(new Uri("http://www.weather.gov")); }); for (int i = 0; i < 100; i++) { Console.WriteLine(i); Thread.Sleep(100); } } 

当您的程序暂时阻塞时,它会在从远程服务器返回结果之前在for循环中恢复执行。

请记住,新的异步API仍然是单线程的。 因此, WebClient().DownloadStringTaskAsync()仍然需要在您的线程上运行,直到请求已准备好并发送到服务器,然后才能await并将执行返回到Main()中的程序流。

我认为您看到的结果是由于需要一些时间来创建并从您的计算机发出请求。 首先,当完成后, DownloadStringTaskAsync的实现可以等待网络IO和远程服务器完成,并可以返回执行给您。

另一方面, RunOrdinaryTask方法只是初始化任务并为其提供工作负载,并告诉它启动。 然后它立即返回。 这就是使用RunOrdinaryTask时没有看到延迟的RunOrdinaryTask

以下是该主题的一些链接: Eric Lippert的博客 (语言设计师之一),以及Jon Skeet关于它的最初博客文章 。 Eric有一系列关于延续传递风格的5篇post,这实际上就是asyncawait的真正含义。 如果您想详细了解新function,可能需要阅读Eric关于CPS和Async的post。 无论如何,上面的两个链接都很好地解释了一个非常重要的事实:

  • 异步!=并行

换句话说, asyncawait不会为您启动新线程。 当你进行阻塞操作时,它们只是让你恢复正常流程的执行 – 你的CPU只会在同步程序中无所事事的时间,等待一些外部操作完成。

编辑

只是要清楚发生了什么: DownloadStringTaskAsync设置一个延续,然后在同一个线程上调用WebClient.DownloadStringAsync然后将执行返回到您的代码。 因此,在循环开始计数之前看到的阻塞时间是DownloadStringAsync完成所需的时间。 你的async和await的程序非常接近于以下程序,它表现出与程序相同的行为:初始块,然后计数开始,在中间的某个地方,async op完成并打印来自的程序请求的url:

  static void Main(string[] args) { WebClient cli = new WebClient(); cli.DownloadStringCompleted += (sender, e) => Console.WriteLine(e.Result); cli.DownloadStringAsync(new Uri("http://www.weather.gov")); // Blocks until request has been prepared for (int i = 0; i < 15; i++) { Console.WriteLine(i); Thread.Sleep(100); } } 

注意:我绝不是这方面的专家,所以在某些方面我可能错了。 如果您认为这是错误的,请随意纠正我对该主题的理解 - 我只是看了PDC演示文稿,并在昨晚与CTP一起玩。

您确定该问题与从IE / Registry / Somewhere Slow检测到的代理配置设置无关吗?

尝试设置webClient.Proxy = null(或在app.config中指定设置),并且“阻塞”期间应该是最小的。

你按F5或CTLR + F5来运行吗? 对于F5,VS只是为了搜索AsyncCtpLibrary.dll的符号而延迟…