Знакомство с асинхронными операциями в C# 5

    В прошлый раз мы говорили о том, как можно упростить работу с асинхронными операциями с помощью класса AsyncEnumerator Джеффри Рихтера, а также с помощью библиотеки Reactive Extensions. Однако, как поведал нам недавно Хейлсберг с компанией, счастье таки наступит с выходом новой версии языка C# и нам не понадобится использовать сторонние и не очень велосипеды, при работе с асинхронными операциями.

    Идея, которая лежит в основе асинхронных операций в C# 5, очень похожа на ту, которую использовал Джеффри Рихтер в своем классе AsyncEnumerator, только на этот раз помимо нас с вами и старины Рихтера о ней еще узнал и компилятор (что здорово сказывается на вкусовых качествах сего синтаксического сахарка).  Для начала, давайте вернемся к синхронной версии нашего гениального примера, который мы использовали ранее:


    1. static void SyncVersion()
    2. {
    3.   Stopwatch sw = Stopwatch.StartNew();
    4.   string url1 = "http://rsdn.ru";
    5.   string url2 = "http://gotdotnet.ru";
    6.   string url3 = "http://blogs.msdn.com";
    7.   var webRequest1 = WebRequest.Create(url1);
    8.   var webResponse1 = webRequest1.GetResponse();
    9.   Console.WriteLine("{0} : {1}, elapsed {2}ms", url1,
    10.     webResponse1.ContentLength, sw.ElapsedMilliseconds);
    11.  
    12.   var webRequest2 = WebRequest.Create(url2);
    13.   var webResponse2 = webRequest2.GetResponse();
    14.   Console.WriteLine("{0} : {1}, elapsed {2}ms", url2,
    15.     webResponse2.ContentLength, sw.ElapsedMilliseconds);
    16.  
    17.   var webRequest3 = WebRequest.Create(url3);
    18.   var webResponse3 = webRequest3.GetResponse();
    19.   Console.WriteLine("{0} : {1}, elapsed {2}ms", url3,
    20.     webResponse3.ContentLength, sw.ElapsedMilliseconds);
    21. }
    * This source code was highlighted with Source Code Highlighter.


    Здесь все очень просто (и главное весьма оригинально!): мы прикидываемся, что мы пишем что-то типа своего браузера, ну или просто нам делать нечего и нужно получить содержимое трех веб-страниц. Синхронная версия, как всегда замечательно работает, за исключением того, что мы ждем втрое больше времени, нежели могли бы, запустив эту операцию асинхронно. Поскольку, мы уже поняли, что это плохо и с удовольствием посмотрели выступление Хейлсберга, то попробуем прямо здесь, так же как и он, двумя росчерками мышки и тремя нажатиями на клаву, сделать из этого замечательного синхронного метода… асинхронный.

    Первое, что нужно сделать, это изменить объявление нашей функции на следующее:

    1. static async void AsyncVersion()
    * This source code was highlighted with Source Code Highlighter.


    Ключевое слово async (которое в CTP версии является ключевым словом, а в релизе будет контекстным ключевым словом) в сигнатуре метода говорит о том, что этот метод выполняется асинхронно и возвращает управление вызывающему коду сразу после начала некоторой асинхронной операции. «Асинхронные методы» могут возвращать один из трех типов возвращаемого значения: void, Task и Task<T>. Если метод возвращает void, то это будет асинхронная операция типа: «запустили и забыли» («fire and forget»), поскольку обработать результат этой операции будет невозможно (хотя если не обработать исключения внутри этого метода, то грохнется он славно, и вполне может потянуть за собой и весь процесс!). В некоторых сценариях это бывает полезным (например, мы захотим асинхронно уведомить всех удаленных подписчиков и нам плевать на то, получат они эти сообщения или нет). С классами Task и Task<T> любознательный читатель (которому еще окончательно не надоели темпы, с которыми всеми нами любимая компания из Редмонда выпускает новые фичи), может быть знаком, поскольку они уже некоторое время живут и здравствуют в .Net Framework 4-й версии. Основная идея этих классов заключается в том, что они в себе инкапсулируют «незавершенную задачу» и мы можем дождаться ее завершения, установить «продолжение» (нечто, что должно быть вызвано при завершении этой задачи) и т.п. При этом класс Task<T> является наследником класса Task и отличается от последнего тем, что позволяет получить возвращаемое значение типа T посредством свойства Result, в то время, как класс Task скорее говорит о том, что некоторая задача возвращает void, и ценна за счет своих побочных эффектов.

    Поскольку мы не просто хотим запустить на выполнение асинхронную операцию, но и получить результаты ее выполнения, но при этом нам важен не сам результат, а побочный эффект… В общем, мы используем тип Task в качестве возвращаемого значения (хотя могли бы спокойно использовать Task<string>, но, в общем, это не столь важно).

    Изменив сигнатуру метода, нужно изменить немного и его тело. Для этого строки вида:

    1. var webResponse1 = webRequest1.GetResponse();
    * This source code was highlighted with Source Code Highlighter.


    Нужно заменить на:

    1. var webResponse1 = await webRequest1.GetResponseAsync();
    * This source code was highlighted with Source Code Highlighter.


    Ключевое слово await (в релизе это тоже будет контекстным ключевым словом) совсем не значит то, что мы залипнем в этой точке кода до тех пор, пока запрошенная асинхронная операция не будет выполнена. Это может сбивать с толку (и Эрик с компанией даже предлагают подыскать другие ключевые слова), но это означает в точности противоположную идею. Ключевое слово await означает, что после начала асинхронной операции (в данном случае после начала асинхронной операции получения WebResponse) метод должен вернуть управление, а продолжить его с этого же места уже после завершения асинхронной операции. Мудрыми словами это называется продолжениями и у Эрика есть с десяток статей, способных здорово вклинить мозги по этому поводу.

    Если не вдаваться в матан, то в данном случае можно провести параллель с блоками итераторов в C# (если вы не знаком с этой темой, то я очень вам рекомендую это исправить), поскольку компилятор в обоих случаях генерирует конечный автомат, который отслеживает, где было прервано выполнение метода, и используется для корректного восстановления выполнения при последующем вызове. Однако, если в случае итераторов возобновление выполнения блока итераторов происходит при последующем вызове метода MoveNext внешним кодом, то асинхронный метод продолжит выполняться автоматом после завершения асинхронной операции. Для этого, в качестве «продолжения» текущей задачи устанавливается текущий метод, который и вызывается при завершении текущей задачи.

    Итак, вот весь измененный метод целиком:

    1. static async Task AsyncVersion()
    2. {
    3.   Stopwatch sw = Stopwatch.StartNew();
    4.   string url1 = "http://rsdn.ru1";
    5.   string url2 = "http://gotdotnet.ru";
    6.   string url3 = "http://blogs.msdn.com";
    7.   
    8.   var webRequest1 = WebRequest.Create(url1);
    9.   Console.WriteLine("Before webRequest1.GetResponseAsync(). Thread Id: {0}",
    10.     Thread.CurrentThread.ManagedThreadId);
    11.  
    12.   var webResponse1 = await webRequest1.GetResponseAsync();
    13.   Console.WriteLine("{0} : {1}, elapsed {2}ms. Thread Id: {3}", url1,
    14.     webResponse1.ContentLength, sw.ElapsedMilliseconds,
    15.     Thread.CurrentThread.ManagedThreadId);
    16.  
    17.   var webRequest2 = WebRequest.Create(url2);
    18.   Console.WriteLine("Before webRequest2.GetResponseAsync(). Thread Id: {0}",
    19.     Thread.CurrentThread.ManagedThreadId);
    20.  
    21.   var webResponse2 = await webRequest2.GetResponseAsync();
    22.   Console.WriteLine("{0} : {1}, elapsed {2}ms. Thread Id: {3}", url2,
    23.     webResponse2.ContentLength, sw.ElapsedMilliseconds,
    24.     Thread.CurrentThread.ManagedThreadId);
    25.  
    26.   var webRequest3 = WebRequest.Create(url3);
    27.   Console.WriteLine("Before webRequest3.GetResponseAsync(). Thread Id: {0}",
    28.     Thread.CurrentThread.ManagedThreadId);
    29.   var webResponse3 = await webRequest3.GetResponseAsync();
    30.   Console.WriteLine("{0} : {1}, elapsed {2}ms. Thread Id: {3}", url3,
    31.     webResponse3.ContentLength, sw.ElapsedMilliseconds,
    32.     Thread.CurrentThread.ManagedThreadId);
    33. }
    * This source code was highlighted with Source Code Highlighter.


    (Если честно, то я еще изменил вывод на консоль, чтобы было видно в каком потоке выполняется метод в данный момент, но это ведь наглядности ради, так что это изменение не в счет. Но в любом случае, старина Хейлсберг таки не соврал, изменений, действительно, минимальное количество).

    И вот как этот метод вызывается:

    1. static void Main(string[] args)
    2. {
    3.   
    4.   try
    5.   {
    6.     Console.WriteLine("Main thread id: {0}", Thread.CurrentThread.ManagedThreadId);
    7.     var task = AsyncVersion();
    8.     Console.WriteLine("Right after AsyncVersion() method call");
    9.     //Ожидаем завершения асинхронной операции
    10.     task.Wait();
    11.     Console.WriteLine("Asyncronous task finished!");
    12.     
    13.   }
    14.   catch(System.AggregateException e)
    15.   {
    16.     //Все исключения в TPL пробрасываются обернутые в AggregateException
    17.     Console.WriteLine("AggregateException: {0}", e.InnerException.Message);
    18.   }
    19.   Console.ReadLine();
    20. }
    * This source code was highlighted with Source Code Highlighter.


    А вот результат его выполнения:

    Main thread id: 10

    Before webRequest1.GetResponseAsync(). Thread Id: 10


    Right after AsyncVersion() method call


    rsdn.ru: 1672, elapsed 657ms. Thread Id: 13

    Before webRequest2.GetResponseAsync(). Thread Id: 13


    gotdotnet.ru: 99470, elapsed 1915ms. Thread Id: 14

    Before webRequest3.GetResponseAsync(). Thread Id: 14


    blogs.msdn.com: 47927, elapsed 2628ms. Thread Id: 15

    Asynchronous task finished!



    А теперь давайте разберем подробно, что происходит внутри этого зверя. Итак, вызов метода AsyncVersion происходит в текущем потоке, и управление возвращается сразу же после первого оператора await. Но прежде чем вернуть управление вызывающему коду, метод AsyncVersion устанавливается в качестве «продолжения» текущей задачи и запоминается место, с которого нужно продолжить выполнение. Эта функция возвращает объект типа Task, для того, чтобы мы смогли дождаться завершения и проверить результаты. Затем, после завершения асинхронной операции, выполнение возобновляется с предыдущего места и, как мы видим, уже в другом потоке. Далее, этот процесс продолжается до тех пор, пока не будет завершена третья асинхронная операция, после чего метод task.Wait вернет управление, и мы увидим на консоли заветное: “Asynchronous task finished!”.

    Обработка ошибок также претерпела некоторых изменений, но также весьма незначительных. Если вы уже знакомы с TPL, то узнаете знакомый класс System.AggregateExcpetion, который «собирает» все исключения и накапливает их у себя внутри. Причина этого заключается в том, что у одной задачи может быть десяток дочерних задач, каждая из которых может содержать еще несколько «подзадач», и каждое задание из этого «дерева» заданий может свалиться с собственным исключением. Именно для решения этой задачи и служит AggregateException, который содержит в себе «дерево» исключений, которое можно легко «выпрямить» с помощью метода Flatten (подробности можно почитать, например, здесь, раздел «Работа с AggregateException»). В общем, если обработка ошибок и усложнилась то не значительно, а сравнивая это с ночным кошмаром, с которым приходится сталкиваться при работе с APM, то такое «усложнение», даже проблемой назвать пальцы не повернутся.

    На самом деле, этот пример не очень-то отличается от синхронного; ведь мы подряд выполняем три асинхронные операции, при этом каждая последующая операция начинается только после завершения предыдущей. Давайте попробуем переписать в новом виде наш второй пример, который одновременно получает результаты от трех веб-страниц и асинхронно записывает результат в файл:

    1. public static async void AsyncVersion2()
    2. {
    3.   Stopwatch sw = Stopwatch.StartNew();
    4.   var urls = new string[] {"http://rsdn.ru", "http://gotdotnet.ru",
    5.     "http://blogs.msdn.com"};
    6.   var tasks = (from url in urls
    7.         let webRequest = WebRequest.Create(url)
    8.         select new {Url = url, Response = webRequest.GetResponseAsync()})
    9.         .ToList();
    10.   var data = await TaskEx.WhenAll(tasks.Select(t=>t.Response));
    11.   var sb = new StringBuilder();
    12.   foreach(var s in tasks)
    13.   {
    14.     sb.AppendFormat("{0}: {1}, elapsed {2}ms. Thread Id: {3}", s.Url,
    15.       s.Response.Result.ContentLength,
    16.       sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId)
    17.       .AppendLine();
    18.   }
    19.   var outputText = sb.ToString();
    20.   Console.WriteLine("Web request results: {0}", outputText);
    21.       
    22.   using (var fs = new FileStream("d:\\results.txt", FileMode.Create,
    23.       FileAccess.Write, FileShare.Write))
    24.   {
    25.     await
    26.     fs.WriteAsync(UnicodeEncoding.Default.GetBytes(outputText), 0,
    27.             UnicodeEncoding.Default.GetByteCount(outputText));
    28.   }
    29.       
    30. }
    * This source code was highlighted with Source Code Highlighter.

    Вот это уже действительно интересно! Мы получили полностью асинхронный код, но при этом он не выглядит так, будто над ним несколько ночей трудилось стадо индусов (а ведь асинхронный код подобной сложности именно так и выглядит). Он понятен и читается аналогично синхронному! (Если кто не верит, то пусть попробует переписать этот код в классическом APM стиле).

    Итак, что мы имеем? Мы имеем штуку, которой действительно удобно пользоваться без боязни отстрелить себе ногу. Кроме того, даже не вдаваясь в серьезные дебри, более или менее понятно, как этим делом пользоваться (мда, хотя к именам придется привыкнуть). И самое интересное, что это далеко не вся функциональность. Я не рассказывал о контекстах синхронизации и о том, как это дело можно здорово использовать с потоком пользовательского интерфейса, о том, что такое TAP (Task-based Asynchronous Pattern), не говорил о том, что все новые асинхронные методы, которые я использовал являются методами расширения и вы можете их добавлять самостоятельно сколько угодно, да и не вдавался в дебри реализации… Но об этом всем, как-нибудь в другой раз! А сегодня, я вам предлагаю, просто скачать эту штуку и попробовать ее самостоятельно!

    (Чуть не забыл, вот url, чтобы вы долго его не искали: msdn.com/vstudio/async)
    Поделиться публикацией
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 34

      0
      это, по-моему, не первая статья на тему await.
      а вот кому вопрос? (с) пластилиновый мужик

      нигде не раскрывается тема асинхронных операций самих по себе — их смысл не столько в том, чтобы ждать ответа, сколько в том, чтобы заняться другими делами.
      1) отсюда мой первый вопрос — какие проблемы предполагается решить await-ом, если поведение по умолчанию аналогично синхронным вызовам? я имею ввиду кроме аргумента «меньше писать кода».
      2) далее второй вопрос — чем ограничено количество одновременно запущенных await-ов?
        0
        1) Поведение по умолчанию не аналогично синхронному случаю, так как await'ом решается проблема комбинации асинхронных вызовов в асинхронный, а данная задача в синхронном случае решается плохо. Я пытался это объяснить в этой ветке (http://habrahabr.ru/blogs/webdev/108241/#comment_3425399)

        2) В своей статье (http://habrahabr.ru/blogs/net/107583/) я уже писал, что код продолжения (после await, но все асинхронных вызовах) выполняется через SynchronizationContext, следовательно, наследуются ограничения конкретной реализации SynchronizationContext.
          0
          Согласен с Вами. Мне тоже кажется, что приведенные примеры (во всяком случае первый) не раскрывают смысла async/await.
          ИМХО, идея состоит в том чтобы имея одну задачу, разделить ее на несколько более мелких, последовательное выполнение которых решает первоначальную задачу. При этом мы выполняем эту задачу параллельно.
          То есть, например есть задача сохранить файл из интернета на диск. Она бьется на две — получить файл из интернета в память, например, и сохранить из памяти на диск. То есть, имеем метод DownloadToFile, внутри которого еще 2 — DownloadToStream, SaveToFile.
          DonwloadToFile помечается async, что говорит, что его надо выполнить асинхронно. Вызов DownloadToStream предваряется await, что говорит, что в этом месте надо вызвать это метод асинхронно, вернуть управление выше, а после завершения вызванного метода продолжить выполнение с этого же места. И все это для того чтобы параллельно делать что-то еще.

          И когда я понял зачем async/await, первой мыслью было «А чем это отличается от того, чтобы все это делать в параллельном Thread, например, (ну или через Task'и)?». Так вот — ничем. Это просто синтаксический сахар. Как и yield — вместо него тоже можно писать свои реализации IEnumerable и IEnumerator. Да это будет удобно, это в итоге убережет от ошибок и «велосипедов», это будет здорово и проще, но в этом нет ничего нового.

          Или я чего-то не заметил?

          ЗЫ. И кстати возвращать можно не только void, Task и Task<T> =)
            0
            Может быть в первом посте из этой серии будет более понятна идея, поскольку здесь всего-лишь асинхронится то, что рассказывалось там, только третьим способом.
            Но, в любом случае пример (который в этой статье и приведен) показывает следующее: нам выгоднее запустить 3 асинхронные операции получения веб-респонса *одновременно*, а не последовательно, поскольку в этом случае результат мы получим тупо быстрее (причем почти в трое), поскольку большую часть времени выполнения этой операции мы просто ждем ответа от сети. И это далеко не одно и тоже, что мы их запустим в другом потоке с помощью пула потоков, поскольку в этом случае выгоды мы вообще не получим. По сути, именно это мы видим в самом «наивном» использовании новой асинхронности: мы по-сути выгоды не получаем, мы просто выполняем все операции асинхронно, возможно даже не используя новые потоки (если запустить эту функцию в обработчике клика мыши, то отзывчивость приложения будет в три раза выше). Выгоду мы получаем только в самом последнем примере, когда все три запроса делаются *параллельно*, что и показано в последнем примере.
              0
              >И это далеко не одно и тоже, что мы их запустим в другом потоке с помощью пула потоков, поскольку в этом случае выгоды мы вообще не получим

              А можно вот это пояснить. Почему не получим?
                0
                Т.е. мы хотим выяснитиь в чем разница между простым вызовом синхронного метода, т.е. SyncVersion() и вызовом его через Thread Pool (например): ThreadPool.QueueUserWorkItem(o=>SyncVersion());?
                Здесь нет разницы, поскольку мы как выполняли этот метод 10 секунд, так и выполняем, мы просто отвязали поток выполнения, от пока, который его инициировал. Я уже писал в комментариях, что главная выгода от асинхронных операций состоит не в этом. Главная выгода состоит в том, что мы можем повысить не только *отзывчивость* пользовательского интерфейса (что более или просто), а повысить эффективность, за счет того, что мы начнем несколько асинхронных операций *одновременно*. В этом случае мы уменьшим время выполнения, например, с 15, до 5 секунд.

                Там же по ссылке вроде бы это описано, да и в комментах об этом уже написано;)
                  0
                  Боюсь показаться занудой, но мне кажется, что вся соль нововведений немного в другом. Запустить параллельно и асинхронно несколько асинхронных операций с обработкой результатов в контексте вызывавшего потока можно было и раньше:

                  public static void Bar() 
                  {
                      var context = SynchronizationContext.Current;
                      new Thread(delegate()
                      {
                          var requestA = HttpWebRequest.Create("http://ya.ru/");
                          var requestB = HttpWebRequest.Create("http://habr.ru/");
                          var futuresA = requestA.BeginGetResponse(delegate { }, null);
                          var futuresB = requestB.BeginGetResponse(delegate { }, null);
                          var resultA = requestA.EndGetResponse(futuresA);
                          var resultB = requestB.EndGetResponse(futuresB);
                          context.Post(delegate
                          {
                              Console.WriteLine(resultA.ContentType);
                              Console.WriteLine(resultB.ContentType);
                          }, null);
                      }).Start();
                  }

                  И нельзя сказать, что данный код настолько плох, чтобы вводить новую конструкцию. Но вот создание собственного асинхронного метода является проблемой, точнее являлось. Раньше, для этого нужно было определить пару методов BeginXXX(), EndXXX() и реализовывать интерфейс IAsyncResult. Await вводит новую технику работы с асинхронными методами, используя которую новый асинхронный метод описывается через комбинацию старых асинхронных/синхронных методов, и это описание максимально приближено к простому описанию функции.

                  То есть async/await в первую очередь упрощает не использование асинхронных методов, а их определение. Во многом это нововведение подобно yield return, которое упрощает создание интераторов.
          –2
          Если не вдаваться в матан

          Это не матан, в крайнем случае алгебра или теория категорий (вспоминаем о монадах).
            0
            алгебра — это матан, хоть и не вышка
              0
              Ахахахахахаха.
              Спасибо за отличный комментарий, теперь мой день пройдёт замечательно!
              +1
              0
              Мы имеем штуку, которой действительно удобно пользоваться без боязни отстрелить себе ногу.

              При наличии тяжелых вычислений в коде между await'ами достаточно просто: дефолтный SynchronizationContext использует ThreadPool, следовательно можно захватить все его потоки, а потом счасливо дебажить=)
                0
                Простите, но как я понял вся суть асинхронных вызовов — это проделать несколько задач в одном потоке… не могли бы немного пояснить по поводу использования ThreadPool и асинхронных операциях — действительно ли есть требование для его использования?
                  0
                  Где конкретно выполняется код асинхронной операции, вызываемой через await не важно, это дело самой асинхронной операции, проблема состоит в том, где обрабатывать callback (код после await) в потоке асинхронной операции или где-то еще. Дизайнеры async/await решили для этого использовать SynchronizationContext потока, который запускает асинхронную операцию. Если это gui приложение, то SynchronizationContext выполняет код в event loop'е, если это обычный поток, в котором SynchronizationContext не переопределен, то код выполняется в ThreadPool. Я об этом писал в статье Async в C# и SynchronizationContext
                    0
                    Да, я статью чиал. Но после нескольких наработок на node.js где по-дефолту асинхронные операции выполняются в одном потоке, я вижу смысл wait-async'ов именно для организации выполнения задач в одном потоке. Кроме того, большинство примеров того же Липперта про CPS/асинки как раз выполняются в одном потоке.

                    Так что лично я если и буду юзать асинки, то буду стараться инкапсулировать выполнение в один поток. Во всяком случае в рамках одной логической задачи. Тогда и задумываться про стандартный ThreadPool не надо )

                    Может я и ошибаюсь, но я как-то ставлю почти как антонимы TPL и async/await в ракурсе исполнения задач — одно раскидывает по потокам, другое наоборот, позволяет ограничится одним потоком. А дальше — смотрим что нам выгодней и используем. Имхо главное — не смешивать их, иначе получим бяку :)

                0
                Я так понял, все что идет после await оно заворачивает в callback, который идет в новом потоке.
                Лучше бы оно callback- и ставило в очередь и все делало в одном потоке.
                Ну или чтобы был выбор.
                  +1
                  Тк так и происходит, callback выполняется через SynchronizationContext.
                    0
                    Точнее, it depends. Если вы запустите подобную операцию в обработке кнопки мыши, то никаких дополнительных потоков не будет, а если в консоли — то будут (см. вывод первого примера).
                    0
                    Ага все почитал про SynchronizationContext.
                    Выбор есть.
                    +2
                    почему бы первый код не изменить на:

                    static void SyncVersion()
                    {
                      Stopwatch sw = Stopwatch.StartNew();
                      string[] urls = new string[]{"http://rsdn.ru", "http://gotdotnet.ru", "http://blogs.msdn.com"}
                      foreach (string url in urls)
                      {
                        DoDownload(url, ref sw)
                      }
                    }

                    void DoDownload(string url, ref Stopwatch sw)
                    {
                      var req = WebRequest.Create(url1);
                      var res = req.GetResponse();

                      Console.WriteLine("{0} : {1}, elapsed {2}ms", url, req.ContentLength, sw.ElapsedMilliseconds);
                    }


                    * This source code was highlighted with Source Code Highlighter.


                    поудобнее помоему и покрасивей.
                      –2
                      Да уж, «навороченный» Linq и var-ы вынесли мне мозг… Неужели через некоторое время все будут так писать? Вторая версия С# была такой простой, такой понятной. В третьей все стало запутываться, в четвертой запуталось окончательно, пятая будет похоже еще хлеще. Видимо, разработчикам языка не давала покоя мысль, что код всем понятен с первого взгляда — и они оторвались по полной на загадочных

                      var tasks = (from url in urls
                      let webRequest = WebRequest.Create(url)
                      select new {Url = url, Response = webRequest.GetResponseAsync()})
                      .ToList();

                      Здесь от классического шарпа разве что ".ToList()".
                        0
                        Не в языке проблема. Я очень рад LINQ многие вещи он позволяет писать быстрее и проще. Это просто автор статьи злоупотребил им. Злоупотребить же можно любой конструкцией.

                        Вот в «Рекомендации NASA по написанию безопасных программ.»
                        Во многом, Си – это ассемблер высокого уровня. Это даёт значительную гибкость и открывает ящик Пандоры с возможными ошибками.

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

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

                          +1
                          Вот блин, я никогда себя фанатом LINQ-а не считал:) А где я тут им злоупотребил? Он может быть непривычен, это да, но читать (если привыкнуть) станет легче. А тут я создаю анонимный тип, который связывает URL и ответ, чтобы когда я буду итерировать ответ не нужно было по индексу обращаться в другую коллекцию, а все было сразу под рукой.
                          Он же читается практически так же как пишется:
                          взять url из urls
                          создать WebRequest
                          сделать анонимный класс с парой полей: URL и web response-ом на этот url
                          дернуть ToList, поскольку без него ленивая природа LINQ-а сделает свое грязное дело в результате чего асинхронные запросы будут выполняться по одному, что прибьет основную от них выгоду.

                          Вроде бы, опять же, не матан:)
                            0
                            Но согласитесь, код приведенный zabr короче, проще, понятнее. LINQ часто упрощает код, но часто усложняет. Тоже с анонимными типами var.

                            Как аналог сложности, можно привести VB. Там вообще весь код читается как предложения, текстом. Но чище он от этого не становится.
                              0
                              Но ведь мы не об этом коде говорим;) Приведенный код — это упрощение синхронной версии, а не асинхронной. Подобный код для асинхронной версии только усложнит код.

                              Кроме того, вы задумывались зачем вообще ввели все эти анонимные делегаты и лямбды? На самом деле, далеко не всегда следует выделять некоторую логику в отдельный метод. Например, в некоторых случаях некоторая логика неотъемлемо связана с другой логикой; она не самостоятельно. Тогда в этом случае выделение отдельного метода лишь уменьшает связанность (cohesion), и мы получаем, что одна, по сути, логика начинает «расползаться» по нескольким методам. Тогда, вместо того, чтобы взять и прочитать все в одном месте, нам нажно шариться по трем десяткам функциям, каждая из которых сама по себе ни имеет ни малейшей ценности. Именно в этих случаях повышение декларативности (с помощью того же линка) начинает играть положительную роль.

                              З.Ы. Анонимные типы с var-ом не имеет ничего общего (да, анонимные типы без var-ов никуда, но это не единственный кейс применения этого ключевого слова). Но фраза «анонимный тип var» — это не корректная фраза.
                                0
                                да, писал про «первый код». ради интереса превращу его в асинхронный )

                                раньше асинхронный код делал через 2 простых класса и статический массив/список откуда брались задания и асинхронно выполнялись )

                                посмотримс новый блекджек )
                                  0
                                  Довольно интересно, даже немного загадочно, и местами оч удобно,
                                  но для себя решил пока остаться на Rx (PFX).

                                  Отсрелить ногу возможность есть в 2ух случаях.

                                  1) в методах типа async нельзя передавать параметры аля ref/out, соответсвенно будут пихать что пихнется )
                                  2) лучше не надо обьединять PFX и async, немножко может взорваться мозг.

                                  Но в любом случае это легче чем использовать асинкстейт, асинкрезалт.

                                  З.Ы.

                                  Класс операций закачки я разделил для удобства по файлам, чтобы не запутаться.
                                  Код может содержать пару одинаковых по сути методов для наглядности.
                                  Архив исходников: — ifolder.ru/20635091

                                  Код:

                                  Файл Program.cs:

                                  using System;
                                  using System.Collections.Generic;
                                  using System.Diagnostics;

                                  namespace Downloader
                                  {
                                    class Program
                                    {
                                      private static readonly List<Utils.UrlExt> Urls =
                                        new List<Utils.UrlExt>
                                          {
                                            new Utils.UrlExt(0, "http://ya.ru"),
                                            new Utils.UrlExt(1, "http://google.ru"),
                                            new Utils.UrlExt(2, "http://habrahabr.ru")
                                          };

                                      private static void Main()
                                      {
                                        Stopwatch sw = new Stopwatch();
                                        sw.Start();

                                        Console.WriteLine("Press any key to start .... ");
                                        Console.ReadKey();
                                        Console.WriteLine(Environment.NewLine);

                                        // ================== sync version ===================
                                        sw.Restart();
                                        Console.WriteLine("Starting Sync Version");
                                        Console.WriteLine();

                                        InnerDownloader.SyncDownload(Urls, sw);

                                        Console.WriteLine("End Sync Version");
                                        Console.WriteLine(Environment.NewLine);
                                        // ================ end sync version =================

                                        // ================== async rx version ===================
                                        sw.Restart();
                                        Console.WriteLine("Starting ASync RX (PFX) Version (.Net 3.5-4.0)");
                                        Console.WriteLine();

                                        InnerDownloader.RxASyncDownload(Urls, sw);

                                        Console.WriteLine("End ASync RX (PFX) Version (.Net 3.5-4.0)");
                                        Console.WriteLine(Environment.NewLine);
                                        // ================ end async rx version =================

                                        // ================== new async version ===================
                                        sw.Restart();
                                        Console.WriteLine("Starting New ASync Version (.Net 5.0)");
                                        Console.WriteLine();

                                        InnerDownloader.NewASyncDownload(Urls, sw);

                                        Console.WriteLine("End New ASync Version (.Net 5.0)");
                                        Console.WriteLine(Environment.NewLine);
                                        // ================ end old async version =================

                                        // ================== new async version v2 ===================
                                        sw.Restart();
                                        Console.WriteLine("Starting New ASync Version (.Net 5.0) v2");
                                        Console.WriteLine();

                                        InnerDownloader.NewASyncDownloadV2(Urls, sw);

                                        Console.WriteLine("End New ASync Version (.Net 5.0) v2");
                                        Console.WriteLine(Environment.NewLine);
                                        // ================ end old async version v2 =================

                                        // ================== new async version v3 ===================
                                        sw.Restart();
                                        Console.WriteLine("Starting New ASync Version (.Net 5.0) v3");
                                        Console.WriteLine();

                                        InnerDownloader.NewASyncDownloadV3(Urls, sw);

                                        Console.WriteLine("End New ASync Version (.Net 5.0) v3");
                                        Console.WriteLine(Environment.NewLine);
                                        // ================ end old async version v3 =================

                                        // ================== new async version v4 ===================
                                        sw.Restart();
                                        Console.WriteLine("Starting ASync RX (PFX) Version (.Net 3.5-4.0) + New ASync Version (.Net 5.0) v4");
                                        Console.WriteLine();

                                        InnerDownloader.NewASyncDownloadV4(Urls, sw);

                                        Console.WriteLine("End ASync RX (PFX) Version (.Net 3.5-4.0) + New ASync Version (.Net 5.0) v4");
                                        Console.WriteLine(Environment.NewLine);
                                        // ================ end old async version v4 =================

                                        Console.WriteLine("Press any key to exit .... ");
                                        Console.ReadKey();
                                      }
                                    }
                                  }

                                  * This source code was highlighted with Source Code Highlighter.


                                  Файл Utils.cs
                                  using System;
                                  using System.IO;
                                  using System.Net;

                                  namespace Downloader
                                  {
                                    public class Utils
                                    {
                                      public static int GetLenght(WebResponse response)
                                      {
                                        string result = "";
                                        var stream = response.GetResponseStream();

                                        if (stream != null)
                                        {
                                          using (var sr = new StreamReader(stream))
                                          {
                                            result = sr.ReadToEnd();
                                          }
                                        }

                                        return result.Length;
                                      }

                                      public class UrlExt
                                      {
                                        public int Index;
                                        public string Url;

                                        internal UrlExt(int index, string url)
                                        {
                                          Index = index;
                                          Url = url;
                                        }
                                      }

                                      public static int DrawTextProgressBar(int progress, int total, string data)
                                      {
                                        progress = progress + 1;

                                        //draw empty progress bar
                                        Console.CursorLeft = 0;
                                        Console.Write("["); //start
                                        Console.CursorLeft = 12;
                                        Console.Write("]"); //end
                                        Console.CursorLeft = 1;
                                        const float onechunk = 3.5F;

                                        //draw filled part
                                        int position = 1;
                                        for (int i = 0; i < onechunk * progress; i++)
                                        {
                                          Console.BackgroundColor = ConsoleColor.Gray;
                                          Console.CursorLeft = position++;
                                          Console.Write(" ");
                                        }

                                        //draw unfilled part
                                        for (int i = position; i <= 11; i++)
                                        {
                                          Console.BackgroundColor = ConsoleColor.Black;
                                          Console.CursorLeft = position++;
                                          Console.Write(" ");
                                        }

                                        //draw totals
                                        Console.CursorLeft = 14;
                                        Console.BackgroundColor = ConsoleColor.Black;
                                        Console.Write("{0} of {1}, {2} ", progress, total, data); //blanks at the end remove any excess

                                        Console.WriteLine(Environment.NewLine);

                                        return 0;
                                      }
                                    }
                                  }


                                  * This source code was highlighted with Source Code Highlighter.


                                  Файл InnerDownloaderSync.cs

                                  using System.Collections.Generic;
                                  using System.Diagnostics;
                                  using System.Net;
                                  using System.Threading;

                                  namespace Downloader
                                  {
                                    /// <summary>
                                    /// Синхронная версия
                                    /// </summary>
                                    public partial class InnerDownloader
                                    {
                                      public static void SyncDownload(List<Utils.UrlExt> urls, Stopwatch sw)
                                      {
                                        for (int index = 0; index < urls.Count; index++)
                                        {
                                          string result = DoSyncDownload(urls[index].Url, sw);
                                          Utils.DrawTextProgressBar(index, urls.Count, result);
                                        }
                                      }

                                      static string DoSyncDownload(string url, Stopwatch sw)
                                      {
                                        var req = (HttpWebRequest)WebRequest.Create(url);
                                        req.AllowAutoRedirect = true;

                                        var res = (HttpWebResponse)req.GetResponse();

                                        return string.Format("{0}: {1}, {2}ms, Thread:{3}", url, Utils.GetLenght(res),
                                                   sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId);

                                      }
                                    }
                                  }


                                  * This source code was highlighted with Source Code Highlighter.


                                  Файл InnerDownloaderRx.cs

                                  using System.Collections.Generic;
                                  using System.Diagnostics;
                                  using System.Threading.Tasks;

                                  namespace Downloader
                                  {
                                    /// <summary>
                                    /// Версия для работы через Reactive, PFX
                                    /// </summary>
                                    public partial class InnerDownloader
                                    {
                                      public static void RxASyncDownload(List<Utils.UrlExt> urls, Stopwatch sw)
                                      {
                                        Parallel.For(0, urls.Count,
                                               index =>
                                               {
                                                 string result = DoSyncDownload(urls[index].Url, sw);
                                                 Utils.DrawTextProgressBar(index, urls.Count, result);
                                               }
                                          );
                                      }
                                    }
                                  }

                                  * This source code was highlighted with Source Code Highlighter.


                                  Файл InnerDownloaderASyncStyleOne.cs

                                  using System.Collections.Generic;
                                  using System.Diagnostics;
                                  using System.Linq;
                                  using System.Net;
                                  using System.Threading;
                                  using System.Threading.Tasks;

                                  namespace Downloader
                                  {
                                    /// <summary>
                                    /// Асинхронная версия стиль № 1
                                    /// </summary>
                                    public partial class InnerDownloader
                                    {
                                      public static async void NewASyncDownload(IEnumerable<Utils.UrlExt> urls, Stopwatch sw)
                                      {
                                        int count = urls.Count();
                                        var tasks =
                                          (
                                            from url in urls
                                            let result = DoASyncDownloadV1(url.Url)
                                            select new
                                            {
                                              Response = result,
                                              Output = Utils.DrawTextProgressBar
                                              (
                                                url.Index, count,
                                                string.Format("{0}: {1}, {2}ms, Thread:{3}",
                                                  url.Url, Utils.GetLenght(result.Result),
                                                  sw.ElapsedMilliseconds, Thread.CurrentThread.ManagedThreadId)
                                              )
                                            }
                                          ).ToList();

                                        TaskEx.WhenAll(tasks.Select(t => t.Response));
                                      }

                                      static Task<WebResponse> DoASyncDownloadV1(string url)
                                      {
                                        var req = (HttpWebRequest)WebRequest.Create(url);
                                        req.AllowAutoRedirect = true;

                                        return req.GetResponseAsync();
                                      }
                                    }
                                  }

                                  * This source code was highlighted with Source Code Highlighter.


                                  Файл InnerDownloaderASyncStyleTwo.cs

                                  using System.Collections.Generic;
                                  using System.Diagnostics;
                                  using System.Linq;
                                  using System.Net;
                                  using System.Threading;
                                  using System.Threading.Tasks;

                                  namespace Downloader
                                  {
                                    /// <summary>
                                    /// Асинхронная версия стиль № 2
                                    /// </summary>
                                    public partial class InnerDownloader
                                    {
                                      public static void NewASyncDownloadV2(List<Utils.UrlExt> urls, Stopwatch sw)
                                      {
                                        var task = InnerNewASyncDownloadV2(urls, sw);
                                        task.Wait();
                                      }

                                      private static async Task InnerNewASyncDownloadV2(List<Utils.UrlExt> urls, Stopwatch sw)
                                      {
                                        int count = urls.Count();
                                        for (int index = 0; index < count; index++)
                                        {
                                          var result = await DoASyncDownloadV2(urls[index].Url);
                                          var data = string.Format("{0}: {1}, {2}ms, Thread:{3}",
                                                       urls[index].Url,
                                                       Utils.GetLenght(result),
                                                       sw.ElapsedMilliseconds,
                                                       Thread.CurrentThread.ManagedThreadId);

                                          Utils.DrawTextProgressBar(index, urls.Count, data);
                                        }
                                      }

                                      static Task<WebResponse> DoASyncDownloadV2(string url)
                                      {
                                        var req = (HttpWebRequest)WebRequest.Create(url);
                                        req.AllowAutoRedirect = true;

                                        return req.GetResponseAsync();
                                      }
                                    }
                                  }

                                  * This source code was highlighted with Source Code Highlighter.


                                  Файл InnerDownloaderASyncStyleThree.cs

                                  using System.Collections.Generic;
                                  using System.Diagnostics;
                                  using System.Net;
                                  using System.Threading;
                                  using System.Threading.Tasks;

                                  namespace Downloader
                                  {
                                    /// <summary>
                                    /// Асинхронная версия стиль № 3
                                    /// </summary>
                                    public partial class InnerDownloader
                                    {
                                      public static void NewASyncDownloadV3(List<Utils.UrlExt> urls, Stopwatch sw)
                                      {
                                        for (int index = 0; index < urls.Count; index++)
                                        {
                                          string result = DoASyncDownloadV3(urls[index].Url, sw);
                                          Utils.DrawTextProgressBar(index, urls.Count, result);
                                        }
                                      }

                                      private static string DoASyncDownloadV3(string url, Stopwatch sw)
                                      {
                                        List<string> output = new List<string> { "" };
                                        var response = InnerNewASyncDownloadV3(url, sw, output);
                                        response.Wait();

                                        return output[0];
                                      }

                                      private static async Task InnerNewASyncDownloadV3(string url, Stopwatch sw, IList<string> output)
                                      {
                                        var result = await DoASyncDownloadV3(url);
                                        output[0] = string.Format("{0}: {1}, {2}ms, Thread:{3}",
                                                    url,
                                                    Utils.GetLenght(result),
                                                    sw.ElapsedMilliseconds,
                                                    Thread.CurrentThread.ManagedThreadId);
                                      }

                                      static Task<WebResponse> DoASyncDownloadV3(string url)
                                      {
                                        var req = (HttpWebRequest)WebRequest.Create(url);
                                        req.AllowAutoRedirect = true;

                                        return req.GetResponseAsync();
                                      }
                                    }
                                  }


                                  * This source code was highlighted with Source Code Highlighter.


                                  Файл InnerDownloaderRxASync.cs

                                  using System.Collections.Generic;
                                  using System.Diagnostics;
                                  using System.Threading.Tasks;

                                  namespace Downloader
                                  {
                                    /// <summary>
                                    /// Асинхронная версия 3 + PFX
                                    /// </summary>
                                    public partial class InnerDownloader
                                    {
                                      public static void NewASyncDownloadV4(List<Utils.UrlExt> urls, Stopwatch sw)
                                      {
                                        Parallel.For(0, urls.Count,
                                               index =>
                                               {
                                                 string result = DoASyncDownloadV3(urls[index].Url, sw);
                                                 Utils.DrawTextProgressBar(index, urls.Count, result);
                                               }
                                          );
                                      }
                                    }
                                  }

                                  * This source code was highlighted with Source Code Highlighter.
                                    0
                                    Букав просто дофига! Не факт, что все понял и осилил, но пару замечаний есть.

                                    1. Я вообще не въехал, что за UrlExt, поскольку я нигде не увидел использование поля Index этого класса.
                                    2. RxASyncDownload никакого отношения к RX не имеет. Там есть PFX, но нет Rx-a (вот тут я писал про rx, глянь последний пример плз). Здесь мы стартанем такое количество задач параллельно, сколько у нас ядер (кажись именно такое поведение по умолчанию в PFX) и здесь, кстати, оно ни к чему, поскольку Parallel.For призвано прежде всего для распараллеливания CPU-bound операций, а у нас здесь IO-bound, так что нам здесь нет смысла ограничиваться количеством параллельных операций, равному количеству ядер процессора (ну, логическому количество процессоров).
                                    3. ИМХО, нет смысла выделять отдельный метод DoASyncDownloadV1, поскольку он не самостоятелен и все время приходится смотреть, что же он такое делает.
                                    4. Не увидел разницы между NewASyncDownload и NewASyncDownloadV2. В первом случае перебор всех урлов производится с помощью LINQ, а во втором случае — с помощью простого цикла. При этом для NewASyncDownloadV2 введено целых два вспомогательных метода, которые нужно анализировать, чтобы понять, что там происходит. Опять ИМХО, это личшее.
                                    5. NewASyncDownloadV3 мне практически вывихнул мозг, поскольку там аж четыре метода. Зачем-то используется List output, который передается другому методу, который изменяет значение по нулевому индексу, и потом это же значение (из нулевого индекса) возвращается из метода DoASyncDownloadV3. И, как я понял, все это для того, чтобы выполнить все эти операции асинхронно, но начинать каждую следующую по завершению предыдущей асинхронной операции.
                                    По сути, это то, что привел в самом первом примере, когда в синхронную версию добавил модификаторов async и await.
                                    6. NewASyncDownloadV4 и я опять не понял в чем смысл? Зачем использовать PFX, когда мы можем просто начать последовательно несколько асинхронных операций и потом уже ждать их завершения? Я например, не знаю, как будет параллелиться этот код:) Вы знаете, сколько операций будет запущено асинхронно одновременно на моем (именно моем) компьютере? Опять повторюсь, Parallel.For — это для параллельных вычислений, а не для параллельного ввода-вывода.

                                    З.Ы. Написано очень много букв и многие из них вполне разумны. Единственное, на что я хочу обратить внимание, так это на то, что разбивать все на методы, которые не самостоятельны не всегда хорошо. Проще, чтобы было сразу же в одном методе видно, что есть что.
                                    А так, молодца!!!
                                      0
                                      1. CNTRL+F рулит :))

                                      public class UrlExt
                                      {
                                      public int Index;
                                      public string Url;

                                      internal UrlExt(int index, string url)
                                      {
                                      Index = index;
                                      Url = url;
                                      }
                                      }

                                      хотя можно и сделать структурой.

                                      2. Я имел ввиду PFX, просто теперь оно входит в Rx, в этом контексте… надо было указать мне )
                                      3. Это задел на будущее для доп обработок, например ошибок или настройки реквеста.
                                      4. визуальное не больше того.
                                      5. да именно так.
                                      6. я посмотрел можно ли это сделать, но выше заметил что нехояшо это.

                                      З.Ы. Визуально мне очень понравился асинхронный вариант №1 у Вас он последний в топике.

                                        0
                                        Капитан, благодарю! Но я правда только сейчас нашел использование поля Index. Оно есть только NewASyncDownload, хотя этот класс используется во всех методах.
                                        2. Rx — ставится отдельно, а PFX входит в состав .net 4.0
                                        3. Понял, но я бы выделил его тогда, когда появится в нем необходимость.
                                        4. Не понял:)
                                        5. Ок
                                        6. Ок

                                        Блин, почему здесь нет смайлов, аналогичных rsdn-овским, вот с таким общением, явно нужен смайл :bear:!
                                          0
                                          2. привычка хех, забывыю периодечиски что входит, поэтому все равно ставлю )
                                          3. да Вы правы, но и в заделе на будущее есть резон.
                                          4. это по сути одно и то же, я просто показываю сразу, что можно так можно так, ничего не имею против LINQ )
                              0
                              Потому что такие операции читабельнее были бы в виде method groups мне кажется.
                              Но это на вкус и цвет.

                              Извините что не написал ИМХО :)
                          +1
                          Вся выгода от *одновременного* выполнения нескольких асинхронных операций. Этим кодом я пытался показать, что у нас три асинхронные операции, а то, что они однотипные — это не важно, ну, просто так получилось.
                          +1
                          стиль изложения потрясен :) я не программист (для себя не считается ;) ), но даже я читал взахлеб.

                          Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                          Самое читаемое