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

age*_*end 15 .net c# asynchronous async-await c#-5.0

出于某种原因,在下面的程序开始后有一个暂停.我相信这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<string> DownloadAndReturnTaskStringAsync()
    {
        return await new WebClient().DownloadStringTaskAsync(new Uri("http://www.weather.gov"));
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

还有一件事.而不是

return await new WebClient().DownloadStringTaskAsync(new Uri("http://www.weather.gov"));
Run Code Online (Sandbox Code Playgroud)

我用自己的方法:

return await ReturnOrdinaryTask();
Run Code Online (Sandbox Code Playgroud)

这是:

public static Task<string> 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;
}
Run Code Online (Sandbox Code Playgroud)

它的工作原理应该如此.我的意思是它不加载任何东西,但它立即启动并且不会阻塞主线程,同时做它的工作.

编辑

好吧,我现在相信的是: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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

dri*_*iis 15

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

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

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

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

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

  • 异步!=并行

换句话说,asyncawait没有为您启动新线程.当你进行阻塞操作时,它们只是让你恢复正常流程的执行 - 你的CPU只会坐在同步程序中什么也不做,等待一些外部操作完成的时间.

编辑

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

    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);
        }
    }
Run Code Online (Sandbox Code Playgroud)

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


Chr*_*ris 5

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

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