C # асинхронные методы все еще вешают интерфейс - PullRequest
9 голосов
/ 17 июля 2011

У меня есть два метода, которые я хочу запустить async, чтобы пользовательский интерфейс реагировал. Тем не менее, он по-прежнему висит интерфейс. Есть предложения?

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 ]

12 голосов
/ 17 июля 2011

Я подозреваю, что DownloadStringTaskAsync полагается на HttpWebRequest.BeginGetResponse на более низком уровне. В этом случае известно, что настройка веб-запроса не является полностью асинхронной. Досадно (и, честно говоря, глупо), фаза поиска DNS асинхронного WebRequest выполняется синхронно, и, следовательно, блокируется. Я подозреваю, что это может быть проблема, которую вы наблюдаете.

Воспроизведено ниже предупреждение в документах:

Метод BeginGetResponse требует выполнения некоторых задач синхронной настройки (например, разрешение DNS, обнаружение прокси-сервера и TCP-сокета), прежде чем этот метод станет асинхронным. В результате этот метод никогда не должен вызываться в потоке пользовательского интерфейса (UI), поскольку это может занять некоторое время, обычно несколько секунд. В некоторых средах, где сценарии веб-прокси не настроены должным образом, это может занять 60 секунд или более. Значение по умолчанию для атрибута downloadTime в элементе файла конфигурации составляет одну минуту, что составляет большую часть потенциальной временной задержки.

У вас есть два варианта:

  1. Запустить запрос из рабочего потока (и при высокой нагрузке запустить риск истощения ThreadPool из-за блокировки)
  2. (незначительно) Выполните программный поиск DNS перед отправкой запроса. Это может быть сделано асинхронно. Надеемся, что тогда запрос будет использовать кэшированный поиск DNS.

Мы выбрали третий (и дорогостоящий) вариант реализации собственной правильно асинхронной библиотеки HTTP, чтобы получить приличную пропускную способность, но в вашем случае это, вероятно, немного экстремально;)

5 голосов
/ 18 июля 2011

Вы, похоже, путаете асинхронность с параллелью.Они оба основаны на задачах, но они совершенно разные.Не думайте, что async методы работают параллельно - они не работают.

Асинхронные по умолчанию работают в том же потоке , если только нет причин, заставляющих асинхронный двигатель вращатьсяновый поток, например, когда основной поток не имеет сообщения сообщений.Но в целом я склонен думать, что ключевое слово async работает в том же потоке.

Вы используете WinForms, поэтому в потоке пользовательского интерфейса есть насос сообщений.Следовательно, весь ваш код, приведенный выше, выполняется в потоке пользовательского интерфейса .

Вы должны понимать, что NOT ввел здесь какой-либо параллелизм.С помощью ключевого слова async вы ввели асинхронные операции, NOT параллельные.Вы не сделали ничего, чтобы «сделать ваш пользовательский интерфейс отзывчивым», за исключением одного звонка на DownloadStringTaskAsync, который не заставит вас ждать поступления данных, но вы STILL должны выполнить всю сетьобработка (поиск DNS и т. д.) в потоке пользовательского интерфейса - вот асинхронная операция в игре (вы, по сути, «экономите» время ожидания загрузок).

Чтобы пользовательский интерфейс реагировал, вам нужно вращатьотнимать трудоемкую работу в отдельном потоке, оставляя поток пользовательского интерфейса свободным.Вы не делаете это с ключевым словом async.

Вам необходимо использовать Task.Factory.StartNew(...), чтобы явно раскрутить новый поток для выполнения фоновой обработки.

4 голосов
/ 17 июля 2011

Сколько элементов вы добавляете в список?

Если вы не предпримете никаких действий, чтобы предотвратить это, представление списка WinForms будет обрабатывать lot каждый раз, когда вы добавляете элемент в список. Это может занять так много времени, что добавление всего 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();
    }
}

Должен наконец попытаться, чтобы избежать всех видов боли, если есть исключение.

...