C#异步方法仍然挂起UI

我有这两种方法,我想运行异步以保持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> GetList() { var client = new WebClient(); string content = await client.DownloadStringTaskAsync(new Uri("anyUrl")); var lstItemObjects = new List(); 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; } } 

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

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

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

你有两个选择:

  1. 从工作线程启动请求(并且在高负载下,由于阻塞行为而冒着ThreadPool饥饿的风险)
  2. (Tenuously)在触发请求之前执行编程式DNS查找。 这可以异步完成。 希望请求将使用缓存的DNS查找。

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

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

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

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

你必须明白,你没有在这里引入任何并行性。 您通过async关键字引入的是异步操作,而不是并行操作。 你没有做任何事情来“让你的UI响应”,除了那个调用DownloadStringTaskAsync之外,它不会强迫你等待数据到达,但是你仍然必须进行所有的网络处理(DNS查找等)。 UI线程 – 这是正在进行的异步操作(你实际上是“节省”等待下载的时间)。

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

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

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

除非您采取措施来阻止它,否则每次将项添加到列表中时,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(); } } 

如果有exception,最后要尝试避免各种各样的痛苦。