Async в C#

    Продолжение по ссылкам: часть II и часть III.

    На PDC2010 Хейсберг объявил, что следующая версия C# будет поддерживать примитивы для удобной организации асинхронных вычислений, кроме анонса была представлена CTP версия расширения для студии (скачать), которая позволяет попробовать улучшенное асинхронное программирование уже сейчас.

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

    Не смотря на то, что упрошенное асинхронное программирование является нововведением в C#, сам подход нельзя назвать инновационным, так как реализация async на основе монад есть в Haskell, F# и Nemerle. На самом деле поддержка языком монад позволяет реализовать даже большее, поэтому я был немного удивлен, когда посмотрел презентацию Хейсберга и понял, что в язык был встроен только частный случай.

    Итак Async!


    Поддержка асинхронного программирования в будущем C# основана на трех нововведениях: типов Task и Task<T>, оператора await и маркера async.

    Task и Task<T>

    Эти типы описывают асинхронное вычисление в процессе выполнения, а так же его результат. Можно провести аналогию с Thread. Ниже приведена часть сигнатуры Task<T>:

    public class Task<TResult> : Task
    {
        public TResult Result { get; internal set; }
        public Task<TNewResult> ContinueWith<TNewResult>(
            Func<Task<TResult>, TNewResult> continuationFunction
        );
    }

    Представим, что мы вызвали метод, который вернул нам Task<string>. Скорее всего, это означает, что вызываемый метод запустил асинхронную операцию, результатом который будет string, и вернул нам управление, а так же объект, описывающий саму асинхронную операцию, не дожидаясь завершения её выполнения. Имея этот объект, мы может обратиться к полю Result, что бы получить результат выполнения операции, в этом случае текущий поток приостановиться, пока асинхронная операция не будет завершена.

    Другая действие, которое мы можем совершить с объектом типа Task<T>, это указать ему, что как только асинхронная операция завершиться, ему нужно запустить continuationFunction с результатом выполнения операции. Таким образом, объединяя несколько асинхронных операций, мы получаем асинхронную операцию.

    В принципе пользоваться таким подходом к организации асинхронных вычислений можно уже сейчас, так как описанные типы принадлежат пространству имен System.Threading.Tasks, которое было введено в .NET Framework 4. Но такое использование не очень удобно, так как один логический метод: получить результат и обработать его, мы должны разбить на метода: один — запустить получение результата, и второй — заняться обработка результата. Поэтому были введены маркер async и оператор await.

    Async и await

    Маркер очень похож на атрибут или модификатор доступа, которые применяются к методу. Вот пример использования:

    public async Task<string> DownloadStringTaskSlowNetworkAsync(Uri address) {

    Маркер может применяться к методу, который возвращает Task<T> или void. Применять маркер к методу нужно, когда в теле метода происходит комбинация других асинхронных вызовов (используется оператор await) или когда метод определяет асинхронную операцию, но это редко требуется, так как в поставляемой с расширением библиотеке AsyncCtpLibrary.dll уже определено большое количество методов для работы с основными асинхронными запросами.

    Последним ключевым объектом, на котором основаны облегченные асинхронные операции — оператор await. Он нужен для объединения последовательности асинхронных операций в одну. Этот оператор принимает на вход объект, описывающий асинхронную операцию, и так переписывает код, что бы все, что следует после выражения с await, преобразовывалось в замыкание и было аргументом метода ContinueWith объекта к которому применяется await. Можно сказать, что это указание компилятору: «Так, все, что идет после, должно выполняться как только текущая асинхронная операция завершится». Отличие от обращения к свойству Result состоит в том, что текущий поток выполнения не приостанавливается, а создается объект описывающий асинхронную операцию и он тут же возвращается.

    Рассмотрим пример, который асинхронно скачивает и сохраняет веб страницу, видно, что код выглядит синхронно, хотя загрузка страницы происходит асинхронно:

    class Program
    {
        static async Task SavePage(string file, string a)
        {
            using (var stream = File.AppendText(file))
            { 
                var html = await new WebClient().DownloadStringTaskAsync(a);
                stream.Write(html);
            }
        }
    
        static void Main(string[] args)
        {
            var task = SavePage("habrahabr", "http://habrahabr.ru");
            task.Wait();
        }
    }

    Компилятор async/await переписывает примерно в следующее:

    static Task SavePage(string file, string a)
    {
        var stream = File.AppendText(file);
        return new WebClient().DownloadStringTaskAsync(a).ContinueWith(delegate(Task<string> data)
        {
            try
            {
                stream.Write(data.Result);
            }
            finally
            {
                stream.Dispose();
            }
        });
    }

    Конечно же, async метод может содержать несколько await вызовов, благодаря этому можно комбинировать асинхронные операции в одну, например, пример с сохранением веб страниц можно переписать с использованием асинхронной записи в файл одним минорным изменением:

    static async Task SavePage(string file, string a)
    {
        using (var stream = File.AppendText(file))
        { 
            var html = await new WebClient().DownloadStringTaskAsync(a);
            await stream.WriteAsync(html);
        }
    }

    Что осталось за кадром

    Я не написал о средствах синхронизации асинхронных операций (Task.WaitOne, Task.WaitAll), а так же о многопоточных приложениях на основе потока данных (System.Threading.Tasks.Dataflow) и информации о ходе выполнения операции. Но стоит отметить, что релиз идет в комплекте с кучей примеров, по которым можно изучить технологию. Для того, что бы было их интереснее изучать, есть задачка: в примере с DiningPhilosophers есть deadlock, нужно найти причину =)
    Share post
    AdBlock has stolen the banner, but banners are not teeth — they will be back

    More
    Ads

    Comments 21

      +1
      Ух ты, вот это действительно удобное нововведение
      0
      Жаль, что все ексепшены из async метода заворачиваются в System.AggregateException. Усложняется запись catch блока.
      • UFO just landed and posted this here
          +3
          Ну уже сейчас есть лямбда-выражения, анонимные делегаты, методы-расширения…
          Если все это бездумно применять везде, где это возможно, код становится абсолютно нечитаемым.

          Microsoft предоставляет богатство синтаксических плюшек, а дело программиста — грамотно ими пользоваться там, где это уместно.

          В Java например до сих пор нет событий, вместо этого используется паттерн observer (интерфейс-listener), у каждого своя политика.

          DependencyProperty, которые появились в WPF, все еще приходится использовать в соответствии с паттерном для обычных геттеров и сеттеров, может когда-нибудь и их включат в синтаксис C#,
          например:

          public dependency bool IsActive { get; set; }
            +4
            Мне кажется, что DependencyProperty никогда не включат в язык, так как изменения для поддержки конкретной прикладной библиотеки ведут в тупик.
              0
              C# и .NET не разделимы, C# изначально предназначен конкретно для платформы .NET.
              уже сейчас есть синтаксические конструкции типа lock, var и т.п., которые в итоге преобразуются в набор стандартных .NET-конструкций (в случае lock — Monitor.Enter() и Monitor.Exit()).

              Если рассматривать базовые классы WPF (DependencyObject, Dependency) не как набор классов для конкретной прикладной библиотеки, а как неотъемлемую часть платформы .NET (также как System.Attribute, System.Enum и т.п.), не вижу ничего преступного в сокращении часто используемых конструкций за счет введения нового ключевого слова в синтаксис C#.

              А пока эти DependencyProperty в коде как раз больше напоминают использование паттерна observer вместо событий в Java.
                0
                Monitor — часть BCL, а WPF — нет (является нестандартной частью .net framework).
                  0
                  Ну хорошо, убедили.
                  Тогда хочу, чтобы среда Visual Studio позволяла удобно создавать и управлять этими самими DependencyProperty в коде. XAML-редактор же сделали они, значит можно и в редакторе кода работу со специфичными частями фреймворка сделать более удобными =)

                  Понятно, что это и сейчас можно сделать самому, написав расширение для Visual Studio, но хочется промышленного решения неудобства.
              0
              В отличие от лямбд async — оружие точечного удара, применить его там, где не уместно, сложно.
                0
                Кстати, вот пример из Nemerle, где поддержка DependencyProperty реализована макросом:

                [DependencyProperty(IsReadOnly)]
                internal DependencyProperty2 : string { get { } set { } }
                +1
                Почему сойдет на нет? Код разбивается на мелкие исполняемые кусочки, у рантайма есть возжность этими кусочками оперировать. Разве что преключения. Но не думаю, что затраты на переключение будут существенней, чем сама операция.
                0
                жаль что язык ява так быстро не развивается :(
                потому что им идиоты командуют
                  0
                  Идиотизм тут не причем.

                  Все дело в том, что язык Java это прежде всего кроссплатформенный открытый стандарт, и существует куча реализаций под различные платформы, поддерживаемые различными вендорами. И при выпуске новых версий языка разработчики максимально стараются сохранить обратную совместимость на уровне виртуальной машины, чтобы новые синтаксические конструкции преобразовывались в «старые» в процессе компиляции. В этом случае не нужно каждый раз «допиливать» JVM под каждую из поддерживаемых платформ.

                  Даже изменение на уровне синтаксиса, которое после компиляции может работать и на «старых» JVM, уже означает необходимость поддержки новых конструкций во всех существующих IDE — NetBeans, Eclipse, IntelliJ IDEA,…

                  У Microsoft ситуация совершенно иная. У них в руках одна платформа CLR — Windows (ну хорошо, еще на Windows Phone 7), одна официальная среда разработки Visual Studio.

                  Да, .NET гораздо мобильнее, Java инертнее. Но их вообще нельзя сравнивать напрямую. Java скорее серверная технология, .NET скорее клиентская.
                    0
                    Радикально .net framwork менялся только при смене версий с 1 на 2.
                      0
                      Согласен. А вот синтаксические плюшки появляются от версии к версии (те же лямбда-выражения, анонимные методы, generics, var, extension-методы, dynamic).

                      В Java этих возможностей гораздо меньше, и появляются они там гораздо медленнее, это факт. Я же хотел указать на то, что это особенность платформы, а не признак их идиотизма.
                      +1
                      Не хотите же вы сказать, что MS никогда за 9-10 лет не меняла формат своего байт-кода? Я не в курсе, но в это не верю…
                      Ничего плохого в том, что нарушится совместимость нет. Пусть успевают за новыми технологиями :)
                      Да и зачем делать костыли: преобразование новых возможнотсей в старый байт-код? Логично и правильно изменить байт-код.
                      Да и какие там разные реализации. Есть одна реализация — Sun JDK. Остальное — такаааая хрень… Вот под OpenJDK Intellij IDEA даже не запускается.
                        0
                        Байт-код тут ни при чем, он как раз не менялся минимум лет пять. Большинство нововведений в языке обеспечиваются библиотеками и компилятором, как и в данном случае.
                          +1
                          если компилятором, а байт-код у них совместимый, значит от новый версий никто не пострадает и не надо будет ничего переделывать каждый раз при выходе новой версии компилятора

                  Only users with full accounts can post comments. Log in, please.