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

时间:2010-10-30 08:41:52

标签: c# .net asynchronous c#-5.0 async-await

出于某种原因,下面的程序启动后会暂停。我相信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"));
    }
}

据我所知,我的程序应该立即从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<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;
}

它可以正常工作。我的意思是它不会加载任何东西,但它会立即启动,并且在执行其工作时不会阻塞主线程。

修改

好的,我现在相信的是: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);
        }
    }

3 个答案:

答案 0 :(得分:15)

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

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

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

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

以下是该主题的一些链接:Eric Lippert's blog(其中一位语言设计师),以及Jon Skeet's initial blog post。 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);
        }
    }

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

答案 1 :(得分:5)

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

尝试设置webClient.Proxy = null(或指定app.config中的设置),您的“阻止”时间应该是最小的。

答案 2 :(得分:2)

您是按F5还是按CTRL + F5来运行它?使用F5,VS的延迟只是为了搜索Async tpLibrary.dll的符号......