c#异步方法仍然挂起UI
本文关键字:挂起 UI 异步方法 | 更新日期: 2023-09-27 18:02:15
我有这两个方法,我想异步运行以保持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;
}
}
我怀疑DownloadStringTaskAsync
在较低水平上依赖于HttpWebRequest.BeginGetResponse
。在这种情况下,我们知道webrequest的设置并不是完全异步的。令人恼火的是(坦率地说,愚蠢地),异步WebRequest的DNS查找阶段是同步执行的,因此阻塞。我怀疑这可能是你正在观察的问题。
下面复制的是文档中的警告:
BeginGetResponse方法需要完成一些同步设置任务(例如,DNS解析、代理检测和TCP套接字连接)才能使该方法变为异步。因此,永远不应该在用户界面(UI)线程上调用此方法,因为它可能需要一些时间,通常是几秒钟。在某些没有正确配置webproxy脚本的环境中,这可能需要60秒或更长时间。配置文件元素上的downloadTime属性的默认值是一分钟,这占了大部分潜在的时间延迟。
你有两个选择:
- 从工作线程启动请求(并且在高负载下,由于阻塞行为而冒线程池耗尽的风险)
- (tenulous)在触发请求之前执行编程式DNS查找。这个可以异步完成。希望请求能够使用缓存的DNS查找。
我们选择了第三种(也是昂贵的)实现我们自己的适当异步HTTP库来获得体面的吞吐量,但对于您的情况可能有点极端;)
你似乎把异步和并行搞混了。它们都基于任务,但它们是完全不同的。不要假设async
方法是并行运行的——它们不是。
Async默认在相同的线程中工作,除非有原因迫使异步引擎启动一个新线程,例如主线程没有消息泵的情况。但是一般来说,我倾向于认为async
关键字在同一个线程中运行。
你使用WinForms,所以UI线程有一个消息泵。因此,上面所有的代码都在UI线程中运行。
您必须明白,这里NOT引入了任何并行性。您通过async
关键字引入的是异步操作,而不是 parallel。你没有做任何事情来"使你的UI响应",除了一个调用DownloadStringTaskAsync
不会强迫你等待数据到达,但是你仍然必须在UI线程中做所有的网络处理(DNS查找等)——这里是异步操作(你基本上"节省"等待下载的时间)。
为了保持UI的响应性,你需要将耗时的工作分离到一个单独的线程中,同时保持UI线程空闲。您不能对async
关键字这样做。
你需要使用Task.Factory.StartNew(...)
来显式地启动一个新线程来做你的后台处理。
您要向列表视图中添加多少项?
除非你采取措施防止它,WinForms列表视图将做一个批次处理每次你添加一个项目到列表中。这可能需要很长时间,添加100项可能需要几秒钟。
尝试在你的循环中使用BeginUpdate
和EndUpdate
来推迟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来避免出现异常时的各种痛苦