【问题标题】:Why does WebClient.DownloadStringTaskAsync() block ? - new async API/syntax/CTP为什么 WebClient.DownloadStringTaskAsync() 阻塞? - 新的异步 API/语法/CTP
【发布时间】:2011-05-02 18:10:30
【问题描述】:

由于某种原因,下面的程序启动后会出现暂停。我相信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 获得)时遇到了同样的问题 - 按下搜索按钮后,用户界面首先冻结 - 一段时间后,它在加载下一部电影时响应。而且我相信它并没有在 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);
        }
    }

【问题讨论】:

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


    【解决方案1】:

    虽然您的程序确实阻塞了一段时间,但它会在从远程服务器返回结果之前在 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 的程序非常接近于以下程序,它表现出与您的程序相同的行为:一个初始块,然后开始计数,在中间的某个地方,异步操作完成并打印来自请求的网址:

        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。

    【讨论】:

    • 是的 - 我看到它像你说的那样工作 - 但我相信这是某种错误 - 想法是像 DownloadStringTaskAsync() 这样的异步操作不会阻塞 main/ui/calling 线程 -所有需要一些时间的事情都应该在其他线程上启动并立即返回(其他方式 - 有什么意义?)。所以问题是:我做错了什么还是 DownloadStringTaskAsync() 方法搞砸了——我现在知道它是 ctp/prototype。但奇怪的是,在演示过程中,它在 Hejlsberg 的计算机上似乎运行良好(没有初始阻塞)。
    • 任何人都可以在他自己的计算机上检查此代码并确认最初的阻止行为吗? TIA 阿雷克
    • 对 - 但我相信 DownloadStringTaskAsync() - 应该启动一个新线程/任务(它确实 - 对吗?) - 并且以一种不会阻塞调用线程的方式进行操作第二 - 你同意我的观点吗?我并不是说新关键字不起作用 - 我是说 DownloadStringTaskAsync() 无法正常工作。
    • @agen:更多信息请参见 Anders Hejlsberg 的演讲。他说明了控制流在11:3038:20 是如何进行的。
    • @driis:您说:“@agend 我认为您的理解不正确-DownloadStringTaskAsync 不会启动一个新线程”-现在您说它确实启动了一个新线程-但它确实“深入肠子”——我说的对吗?我有没有说 async 关键字启动新线程?我要说的是 WebClient.DownloadStringTaskAsync() 的工作方式并不完全像它应该的那样 - 你同意我的观点吗?
    【解决方案2】:

    你是按 F5 还是 CTLR+F5 来运行它?使用 F5 时,VS 会延迟搜索 AsyncCtpLibrary.dll 的符号...

    【讨论】:

    • f5 或 ctrl+f5 无关紧要 - 工作方式相同 - 延迟约为 5 秒,所以我认为这不是符号问题。我在 wpf netflix_async_with_await 示例中具有与 cts 相同的行为。
    • 此示例的silverlight 版本似乎工作正常 - 它最初不会冻结 - 但由于某种原因它也不显示电影图标
    • 当您在本地运行 silverlight 项目时,它只会显示图像,如果它是 ASP 项目/网站的一部分。
    • @agen,我没有在我的计算机上看到阻止行为。另一种可能性是您有某种代理或防火墙,这使得发起请求需要时间。
    • 好的 - 在 silverlight 版本中,当我右键单击 TutoralCSTestPage -> 在浏览器中查看 - 一切正常 - 没有冻结,我可以看到图片。当我通过运行(f5 或 ctrl+f5 和在 TutorialCS 上设置的启动项目(silverlight 应用程序))启动应用程序时,我看不到图片 - 有人知道为什么 - 对我来说似乎完全陌生。
    【解决方案3】:

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

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

    【讨论】:

    • 我正好遇到了这个问题
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-03-09
    • 1970-01-01
    • 2019-10-16
    • 1970-01-01
    • 2012-01-15
    • 1970-01-01
    • 2011-05-09
    相关资源
    最近更新 更多