.NET
3 November 2010

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, нужно найти причину =)

+39
91.1k 167
Comments 21
Top of the day