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

标签 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 获得)我遇到了同样的问题 - 在按下搜索按钮后,用户界面首先卡住 - 一段时间后它在加载下一部电影时响应。我相信它并没有在 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);
        }
    }

最佳答案

虽然您的程序会阻塞一段时间,但在从远程服务器返回结果之前,它会在 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 的程序非常接近于等效于以下程序,它表现出与您的程序相同的行为:一个初始 block ,然后开始计数,在中间的某个地方,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。

关于c# - 为什么 WebClient.DownloadStringTaskAsync() 会阻塞? - 新的异步 API/语法/CTP,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4057926/

相关文章:

c# - 从 Web.Config <appSettings> 部分 C#/MVC 连接到数据库

c# - 在 ASPxTextboxControl 上触发验证后触发 Javascript 函数

c# - 将数据收集到一个对象中并在 .NET/C# 中对其进行排序

c# - 根据文本确定自定义消息框的窗口大小

c# - 如何以及何时使用 ‘async’ 和 ‘await’

c# - UserControl 的基类属于哪一层?

c# - 将 CaSTLe ActiveRecord 与 Fluent NHibernate 结合使用?

c# - 在 Internet Explorer 控件中禁用上下文菜单

python - 在 RQ 中重试失败的作业

node.js - Node 使用回调或事件异步提取 zip 文件?