繁体   English   中英

C# 异步方法仍然挂起 UI

[英]C# async methods still hang 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<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 属性的默认值是一分钟,这占了大部分潜在的时间延迟。

你有两个选择:

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

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

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

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

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

您必须了解您没有在此处引入任何并行性。 您通过async关键字引入的是异步操作,而不是并行操作。 除了对DownloadStringTaskAsync的一次调用之外,您还没有做任何事情来“使您的 UI 响应”,这不会强迫您等待数据到达,但您仍然必须执行所有网络处理(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();
    }
}

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM