Почему WebClient.DownloadStringTaskAsync() заблокирован? - новый асинхронный API/синтаксис/CTP

По какой-то причине происходит пауза после запуска программы. Я считаю, что причина WebClient().DownloadStringTaskAsync().

class Program
{
    static void Main(string[] args)
    {
        AsyncReturnTask();

        for (int i = 0; i < 15; i++)
        {
            Console.WriteLine(i);
            Thread.Sleep(100);
        }
    }

    public static async void AsyncReturnTask()
    {
        var result = await DownloadAndReturnTaskStringAsync();
        Console.WriteLine(result);
    }

    private static async Task<string> DownloadAndReturnTaskStringAsync()
    {
        return await new WebClient().DownloadStringTaskAsync(new Uri("http://www.weather.gov"));
    }
}

Насколько я понимаю, моя программа должна начинать считать от 0 до 15 сразу. Я что-то делаю неправильно?

У меня была та же проблема с исходным образцом загрузки Netflix (который вы получаете с CTP) - после нажатия кнопки поиска пользовательский интерфейс сначала замерзает - и через некоторое время он реагирует при загрузке следующих фильмов. И я считаю, что это не замерзло в презентации Андерса Хейльсберга на PDC 2010.

Еще одна вещь. Когда вместо

return await new WebClient().DownloadStringTaskAsync(new Uri("http://www.weather.gov"));

Я использую свой собственный метод:

return await ReturnOrdinaryTask();

Что есть:

public static Task<string> ReturnOrdinaryTask()
{
    var t = Task.Factory.StartNew(() =>
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("------------- " + i.ToString());
            Thread.Sleep(100);
        }
        return "some text";
    });
    return t;
}

Он работает так, как должен. Я имею в виду, что он ничего не загружает, но начинается сразу и не блокирует основной поток, выполняя свою работу.

Edit

Хорошо, теперь я верю: функция WebClient.DownloadStringTaskAsync запутана. Он должен работать без начального периода блокировки, например:

    static void Main(string[] args)
    {
        WebClient cli = new WebClient();
        Task.Factory.StartNew(() =>
            {
                cli.DownloadStringCompleted += (sender, e) => Console.WriteLine(e.Result);
                cli.DownloadStringAsync(new Uri("http://www.weather.gov"));
            });

        for (int i = 0; i < 100; i++)
        {
            Console.WriteLine(i);
            Thread.Sleep(100);
        }
    }

Ответы

Ответ 1

Пока ваша программа блокируется некоторое время, она возобновляет выполнение в цикле for, пока результат не будет возвращен с удаленного сервера.

Помните, что новый асинхронный API по-прежнему однопоточен. Таким образом, WebClient().DownloadStringTaskAsync() по-прежнему нужно запускать в вашем потоке до тех пор, пока запрос не будет подготовлен и отправлен на сервер, прежде чем он сможет await и вернуть исполнение в поток вашей программы в Main().

Я думаю, что результаты, которые вы видите, связаны с тем, что для создания и отправки запроса с вашего компьютера требуется некоторое время. Во-первых, когда это завершено, реализация DownloadStringTaskAsync может дождаться завершения ввода IO сети и удаленного сервера и может вернуть вам выполнение.

С другой стороны, ваш метод RunOrdinaryTask просто инициализирует задачу и дает ей рабочую нагрузку и сообщает ей, чтобы она начиналась. Затем он немедленно возвращается. Вот почему вы не видите задержку при использовании RunOrdinaryTask.

Вот несколько ссылок на эту тему: блог Эрика Липперта (один из разработчиков языка), а также Начальный пост в блоге Jon Skeet. У Эрика есть сериал из 5 сообщений о стиле продолжения, который на самом деле является тем, о чем говорят async и await. Если вы хотите подробно ознакомиться с новой функцией, вы можете прочитать сообщения Eric о CPS и Async. В любом случае, обе ссылки, приведенные выше, отлично справляются с объяснением очень важного факта:

  • Асинхронный!= параллельный

Другими словами, async и await не разворачивают новые потоки для вас. Они просто позволяют возобновить выполнение вашего обычного потока, когда вы выполняете операцию блокировки - времена, когда ваш процессор просто сидел и ничего не делал в синхронной программе, ожидая завершения внешней операции.

Изменить

Просто чтобы понять, что происходит: DownloadStringTaskAsync устанавливает продолжение, затем вызывает WebClient.DownloadStringAsync, в том же потоке, а затем возвращает выполнение кода. Поэтому время блокировки, которое вы видите перед запуском цикла, - это время, необходимое для завершения DownloadStringAsync. Ваша программа с асинхронным и ожидающим очень близка к эквиваленту следующей программы, которая проявляет то же поведение, что и ваша программа: начальный блок, затем подсчет начинается и где-то посередине, асинхронный режим завершает и печатает содержимое с запрошенный URL:

    static void Main(string[] args)
    {
        WebClient cli = new WebClient();
        cli.DownloadStringCompleted += (sender, e) => Console.WriteLine(e.Result);
        cli.DownloadStringAsync(new Uri("http://www.weather.gov")); // Blocks until request has been prepared

        for (int i = 0; i < 15; i++)
        {
            Console.WriteLine(i);
            Thread.Sleep(100);
        }
    }

Примечание. Я никоим образом не специалист по этому вопросу, поэтому я могу ошибаться в некоторых вопросах. Не стесняйтесь исправить мое понимание предмета, если вы считаете, что это неправильно - я просто посмотрел презентацию PDC и сыграл с CTP прошлой ночью.

Ответ 2

Вы уверены, что проблема не связана с параметрами настройки прокси-сервера, обнаруженными в IE/Registry/Somewhere Slow?

Попробуйте установить webClient.Proxy = null (или указать настройки в app.config), и ваш период блокировки должен быть минимальным.

Ответ 3

Вы нажимаете F5 или CTLR + F5 для запуска? С F5 существует задержка для VS только для поиска символов для AsyncCtpLibrary.dll...