9

我有这两种方法,我想运行异步以保持 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;
        }
    }
4

3 回答 3

12

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

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

BeginGetResponse 方法需要在此方法变为异步之前完成一些同步设置任务(例如 DNS 解析、代理检测和 TCP 套接字连接)。因此,永远不应在用户界面 (UI) 线程上调用此方法,因为它可能需要一些时间,通常是几秒钟。在某些未正确配置 webproxy 脚本的环境中,这可能需要 60 秒或更长时间。配置文件元素的 downloadTime 属性的默认值是一分钟,这占了大部分潜在的时间延迟。

你有两个选择:

  1. 从工作线程启动请求(在高负载下,由于阻塞行为,存在线程池饥饿的风险)
  2. (小心翼翼地)在触发请求之前执行程序化 DNS 查找。这可以异步完成。希望该请求随后将使用缓存的 DNS 查找。

我们选择了实现我们自己的正确异步 HTTP 库以获得不错的吞吐量的第 3 个(也是昂贵的)选项,但在您的情况下它可能有点极端;)

于 2011-07-17T19:47:11.230 回答
5

您似乎将异步与并行混淆了。它们都是基于任务的,但它们完全不同。不要假设async方法是并行运行的——它们不是。

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

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

您必须了解您没有在此处引入任何并行性。您通过async关键字引入的是异步操作,而不是并行操作。您没有做任何事情来“使您的 UI 响应”,除了一个DownloadStringTaskAsync不会强迫您等待数据到达的调用,但您仍然必须在UI 线程——这里是异步操作(你基本上“节省”了等待下载的时间)。

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

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

于 2011-07-18T02:02:52.240 回答
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();
    }
}

如果有异常,必须使用 try finally 来避免各种痛苦。

于 2011-07-17T19:52:53.640 回答