C#异步方法仍然挂起UI

时间:2011-07-17 19:14:04

标签: c# asynchronous webclient async-ctp c#-5.0

我有这两种方法,我想运行异步来保持UI响应。但是,它仍然悬挂着UI。有什么建议吗?

async void DoScrape()
    {
        var feed = new Feed();

        var results = await feed.GetList();
        foreach (var itemObject in results)
        {
            var item = new ListViewItem(itemObject.Title);
            item.SubItems.Add(itemObject.Link);
            item.SubItems.Add(itemObject.Description);
            LstResults.Items.Add(item);
        }
    }


    public class Feed
    {
        async public Task<List<ItemObject>> GetList()
        {
            var client = new WebClient();
            string content = await client.DownloadStringTaskAsync(new Uri("anyUrl"));
            var lstItemObjects = new List<ItemObject>();
            var feed = new XmlDocument();
            feed.LoadXml(content);
            var nodes = feed.GetElementsByTagName("item");

            foreach (XmlNode node in nodes)
            {
                var tmpItemObject = new ItemObject();
                var title = node["title"];
                if (title != null) tmpItemObject.Title = title.InnerText;
                var link = node["link"];
                if (link != null) tmpItemObject.Link = link.InnerText;
                var description = node["description"];
                if (description != null) tmpItemObject.Description = description.InnerText;
                lstItemObjects.Add(tmpItemObject);
            }
            return lstItemObjects;
        }
    }

3 个答案:

答案 0 :(得分:12)

我怀疑DownloadStringTaskAsync在较低级别依赖HttpWebRequest.BeginGetResponse。在这种情况下,已知webrequest的设置不是完全异步的。恼人地(坦率地说,愚蠢地)异步WebRequest的DNS查找阶段是同步执行的,因此会阻塞。我怀疑这可能是你正在观察的问题。

以下转载的是文档中的警告:

  

The BeginGetResponse method requires some synchronous setup tasks to complete (DNS resolution, proxy detection, and TCP socket connection, for example) before this method becomes asynchronous. As a result, this method should never be called on a user interface (UI) thread because it might take some time, typically several seconds. In some environments where the webproxy scripts are not configured properly, this can take 60 seconds or more. The default value for the downloadTime attribute on the config file element is one minute which accounts for most of the potential time delay.

你有两个选择:

  1. 从工作线程启动请求(并且在高负载下,由于阻塞行为而冒着ThreadPool饥饿的风险)
  2. (Tenuously)在触发请求之前执行编程式DNS查找。这个可以异步完成。希望请求将使用缓存的DNS查找。
  3. 我们选择了实现我们自己的正确异步HTTP库的第三个(也是代价高昂的)选项来获得不错的吞吐量,但在你的情况下它可能有点极端;)

答案 1 :(得分:5)

你似乎混淆了并行异步。它们都基于任务,但它们完全不同。不要假设async方法并行运行 - 它们不会。

异步默认工作在相同的线程,除非有理由强制异步引擎启动新线程,例如主线程没有消息泵的情况。但总的来说,我倾向于认为async关键字在同一个线程中运行。

您使用WinForms,因此UI线程有一个消息泵。因此,上面的所有代码都在UI线程中运行

您必须了解此处 NOT 引入了任何并行性。您通过async关键字引入的内容是异步操作, NOT 并行。您没有采取任何措施来“让您的用户界面响应”除了对DownloadStringTaskAsync的一次调用,这不会强迫您等待数据到达,但您仍然必须做UI线程中的所有网络处理(DNS查找等) - 这是正在进行的异步操作(基本上“节省”等待下载的时间)。

为了保持UI的响应性,您需要将耗时的工作分拆到单独的线程中,同时保持UI线程的自由。您没有使用async关键字执行此操作。

您需要使用Task.Factory.StartNew(...)显式启动新线程来进行后台处理。

答案 2 :(得分:4)

您在列表视图中添加了多少项?

除非您采取措施阻止它,否则每次将项目添加到列表中时,WinForms列表视图都会执行批次处理。这可能需要很长时间才能添加100个项目可能需要几秒钟。

尝试在循环中使用BeginUpdateEndUpdate来推迟ListView的簿记,直到完成为止。

async void DoScrape()
{
    var feed = new Feed();

    var results = await feed.GetList();
    LstResults.BeginUpdate();  // Add this
    try
    {
        foreach (var itemObject in results)
        {
            var item = new ListViewItem(itemObject.Title);
            item.SubItems.Add(itemObject.Link);
            item.SubItems.Add(itemObject.Description);
            LstResults.Items.Add(item);
        }
    }
    finally
    {
        LstResults.EndUpdate();
    }
}

如果出现异常,最后要尝试避免各种各样的痛苦。