Предоставление задач .NET в виде асинхронных операций WinRT

В записи блога Подробное рассмотрение WinRT и await мы рассмотрели новые ключевые слова async и await в C# и Visual Basic и их применение для работы с асинхронными операциями среды выполнения Windows (WinRT).

Эти ключевые слова, подключив библиотеки .NET BCL, также можно использовать для разработки асинхронных операций, которые будут предоставляться через WinRT другим компонентам, написанным на других языках. В данной статье мы покажем, как это сделать. (Общие сведения о реализации компонентов WinRT на языках C# и Visual Basic см. в статье Создание компонентов среды выполнения Windows на языках C# и Visual Basic.)

Давайте начнем с рассмотрения асинхронных API в WinRT.

Асинхронные интерфейсы в WinRT

В WinRT для асинхронных операций используется несколько интерфейсов. Первым из них является IAsyncInfo, который должен быть реализован в каждой асинхронной операции WinRT. Этот интерфейс охватывает основные возможности асинхронной операции, в том числе ее текущее состояние, ошибку операции в случае сбоя, идентификатор операции и возможность отмены операции по запросу:

 public interface IAsyncInfo
{
    AsyncStatus Status { get; }
    Exception ErrorCode { get; }
    uint Id { get; }

    void Cancel();
    void Close();
}

Помимо интерфейса IAsyncInfo в каждой асинхронной операции WinRT должен быть реализован один из четырех дополнительных интерфейсов: IAsyncAction, IAsyncActionWithProgress<TProgress> , IAsyncOperation<TResult> или IAsyncOperationWithProgress<TResult,TProgress> . Эти дополнительные интерфейсы позволяют объекту-получателю задавать метод обратного вызова, который будет вызываться по завершении асинхронной операции, а также (дополнительно) получать результат выполнения операции или отчеты о ходе ее выполнения:

 // No results, no progress
public interface IAsyncAction : IAsyncInfo
{
    AsyncActionCompletedHandler Completed { get; set; }
    void GetResults();
}

// No results, with progress
public interface IAsyncActionWithProgress<TProgress> : IAsyncInfo
{
    AsyncActionWithProgressCompletedHandler<TProgress> Completed { get; set; }
    AsyncActionProgressHandler<TProgress> Progress { get; set; }
    void GetResults();
}

// With results, no progress
public interface IAsyncOperation<TResult> : IAsyncInfo
{
    AsyncOperationCompletedHandler<TResult> Completed { get; set; }
    TResult GetResults();
}

// With results, with progress
public interface IAsyncOperationWithProgress<TResult,TProgress> : IAsyncInfo
{
    AsyncOperationWithProgressCompletedHandler<TResult,TProgress> Completed { get; set; }
    AsyncOperationProgressHandler<TResult,TProgress> Progress { get; set; }
    TResult GetResults();
}

(интерфейс IAsyncAction предоставляет метод GetResults, даже если результаты не возвращаются. Это позволяет при сбое асинхронной операции вызывать исключение, не заставляя все объекты-получатели принудительно обращаться к свойству ErrorCode интерфейса IAsyncInfo. Аналогичным образом типы awaiter в C# и Visual Basic предоставляют метод GetResult, даже если метод GetResult возвращает значение типа void.)

В библиотеке WinRT все общедоступные асинхронные операции строго типизированы и возвращают один из указанных выше четырех интерфейсов. В отличие от этих операций новые асинхронные операции из библиотек .NET соответствуют асинхронному шаблону на основе задач (TAP) и возвращают Task (для операций, которые не возвращают результат) или Task<TResult> (для операций, которые возвращают результат).

Типы Task и Task<TResult> не реализуют указанные интерфейсы WinRT и не поддерживают неявное преобразование среды CLR (как это имеет место для некоторых типов, например для типа WinRT Windows.Foundation.Uri и типа System.Uri библиотеки BCL). Вместо этого нам необходимо явным образом преобразовать один мир в другой. В статье Подробное рассмотрение WinRT и await мы показали, как с помощью метода расширения AsTask в библиотеке BCL можно явным образом преобразовать асинхронные интерфейсы WinRT в задачи .NET. Библиотека BCL также поддерживает и обратное преобразование задач .NET в асинхронные интерфейсы WinRT с помощью соответствующих методов.

Преобразование с помощью методов AsAsyncAction и AsAsyncOperation

Предположим, что у нас есть асинхронный метод .NET DownloadStringAsyncInternal. Мы передаем ему URL-адрес веб-страницы, и метод асинхронно загружает и возвращает содержимое этой страницы в виде строки:

 internal static Task<string> DownloadStringAsyncInternal(string url);

Реализация этого метода значения не имеет. Наша цель — представить этот метод в виде асинхронной операции WinRT, т. е. в виде метода, возвращающего один из четырех описанных выше интерфейсов. Поскольку наша операция возвращает результат (строку) и не поддерживает отчеты о ходе выполнения, асинхронная операция WinRT будет возвращать интерфейс IAsyncOperation<string> :

 public static IAsyncOperation<string> DownloadStringAsync(string url);

Для реализации этого метода мы вызываем метод DownloadStringAsyncInternal, чтобы получить необходимое значение Task<string> . Затем нам необходимо преобразовать эту задачу в интерфейс IAsyncOperation<string> … но как это сделать?

 public static IAsyncOperation<string> DownloadStringAsync(string url)
{
    Task<string> from = DownloadStringAsyncInternal(url);
    IAsyncOperation<string> to = ...; // TODO: how do we convert 'from'?
    return to;
}

Для этого в сборку System.Runtime.WindowsRuntime.dll для платформы .NET 4.5 включены методы расширения для Task и Task<TResult> , выполняющие необходимые преобразования:

 // in System.Runtime.WindowsRuntime.dll
public static class WindowsRuntimeSystemExtensions 
{
    public static IAsyncAction AsAsyncAction(
        this Task source);
    public static IAsyncOperation<TResult> AsAsyncOperation<TResult>(
        this Task<TResult> source);
    ...
}

Эти методы возвращают экземпляры интерфейсов IAsyncAction и IAsyncOperation<TResult> , которые представляют собой оболочку для Task и Task<TResult> соответственно (поскольку Task<TResult> выводится из Task, оба метода можно использовать для Task<TResult> , хотя AsAsyncAction относительно редко используется с асинхронным методом, возвращающим результат). Логично представить эти операции как явные синхронные операции приведения типов или (в контексте шаблона проектирования) как адаптеры. Они возвращают экземпляр интерфейса, представляющий соответствующую задачу, но с необходимой контактной зоной для WinRT. Вооружившись этими методами расширения, мы можем завершить реализацию метода DownloadStringAsync:

 public static IAsyncOperation<string> DownloadStringAsync(string url)
{
    Task<string> from = DownloadStringAsyncInternal(url);
    IAsyncOperation<string> to = from.AsAsyncOperation();
    return to;
}

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

 public static IAsyncOperation<string> DownloadStringAsync(string url)
{
    return DownloadStringAsyncInternal(url).AsAsyncOperation();
}

Метод DownloadStringAsyncInternal вызывается перед вызовом метода AsAsyncOperation. Это означает, что нам необходимо выполнить синхронный вызов метода DownloadStringAsyncInternal, чтобы быстро получить результат для обеспечения работоспособности метода-оболочки DownloadStringAsync. Если по каким-либо причинам вы опасаетесь, что синхронная работа займет слишком много времени, или хотите явным образом делегировать вызов пулу потоков, это можно сделать с помощью метода Task.Run с последующим вызовом метода AsAsyncOperation для возвращенной задачи:

 public static IAsyncOperation<string> DownloadStringAsync(string url)
{
    return Task.Run(()=>DownloadStringAsyncInternal(url)).AsAsyncOperation();
}

Большая гибкость благодаря AsyncInfo.Run

Встроенные методы расширения AsAsyncAction и AsAsyncOperation отлично подходят для простых преобразований из Task в IAsyncAction и из Task<TResult> в IAsyncOperation<TResult> . А как обстоит дело с более сложными преобразованиями?

В библиотеке System.Runtime.WindowsRuntime.dll содержится другой тип, обеспечивающий более высокую гибкость: AsyncInfo в пространстве имен System.Runtime.InteropServices.WindowsRuntime. Метод AsyncInfo предоставляет четыре перегруженных варианта статического метода Run, по одному на каждый асинхронный интерфейс WinRT:

 // in System.Runtime.WindowsRuntime.dll
public static class AsyncInfo 
{
    // No results, no progress
    public static IAsyncAction Run(
        Func<CancellationToken, 
             Task> taskProvider); 

    // No results, with progress
    public static IAsyncActionWithProgress<TProgress> Run<TProgress>(
        Func<CancellationToken, 
             IProgress<TProgress>, 
             Task> taskProvider);


    // With results, no progress
    public static IAsyncOperation<TResult> Run<TResult>(
        Func<CancellationToken, 
             Task<TResult>> taskProvider);

    // With results, with progress
    public static IAsyncOperationWithProgress<TResult, TProgress> Run<TResult, TProgress>(
        Func<CancellationToken, 
             IProgress<TProgress>, 
             Task<TResult>> taskProvider);
}

Рассмотренные выше методы AsAsyncAction и AsAsyncOperation принимают в качестве аргумента значение Task. В отличие от них перегруженные методы Run принимают делегат функции, возвращающий значение Task, и этой разницы между Task и Func<…,Task> оказывается достаточно для обеспечения дополнительной гибкости, которая требуется для более сложных операций.

Логично представить методы AsAsyncAction и AsAsyncOperation как простые вспомогательные механизмы, работающие поверх более сложного метода AsyncInfo.Run:

 public static IAsyncAction AsAsyncAction(
    this Task source)
{
    return AsyncInfo.Run(_ => source);
}

public static IAsyncOperation<TResult> AsAsyncOperation<TResult>(
    this Task<TResult> source)
{
    return AsyncInfo.Run(_ => source);
}

Это не совсем точное представление (в .NET 4.5 они реализованы по-другому), однако функционально они работают именно таким образом, что позволяет их так представить, чтобы разграничить поддержку простых и сложных операций. В простых случаях используйте методы AsAsyncAction и AsAsyncOperation, в более сложных — метод AsyncInfo.Run.

Отмена

Метод AsyncInfo.Run позволяет обеспечивать поддержку отмены для асинхронных методов WinRT.

Продолжая наш пример с загрузкой, предположим, что у нас есть перегруженный метод DownloadStringAsyncInternal, принимающий значение CancellationToken:

 internal static Task<string> DownloadStringAsyncInternal(
    string url, CancellationToken cancellationToken);

CancellationToken — это тип .NET Framework, поддерживающий согласованную отмену в компонуемом режиме. Вы можете передать один и тот же маркер в любое число вызовов методов, а при запросе на отмену этого маркера (через объект CancellationTokenSource, который создал маркер) этот запрос будет в дальнейшем виден всем операциям-получателям. Такой подход слегка отличается от подхода, принятого для асинхронных операций WinRT, при котором каждый интерфейс IAsyncInfo предоставляет собственный метод Cancel. Как же с учетом вышесказанного выполнить вызов метода Cancel интерфейса IAsyncOperation<string> , чтобы запросить отмену маркера CancellationToken, переданного методу DownloadStringAsyncInternal? Метод AsAsyncOperation в этом случае не поможет:

 public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
     return DownloadStringAsyncInternal(uri, … /* what goes here?? */)
            .AsAsyncOperation();
}

Чтобы узнать о запросе отмены для IAsyncOperation<string> , этот экземпляр должен каким-то образом уведомить прослушиватель о том, что был вызван метод Cancel, например путем запроса отмены маркера CancellationToken, который мы передали методу DownloadStringAsyncInternal. Однако тут возникает замкнутый круг: мы не можем получить объект Task<string> , для которого необходимо вызвать метод AsAsyncOperation, пока не вызовем метод DownloadStringAsyncInternal, которому необходимо предоставить тот самый маркер CancellationToken, который должен вернуть метод AsAsyncOperation.

Эту головоломку можно решить несколькими способами, в том числе с помощью метода AsyncInfo.Run. Метод Run отвечает за создание интерфейса IAsyncOperation<string> и создает этот экземпляр для запроса отмены маркера CancellationToken, который он также создает при вызове метода асинхронной операции Cancel. Затем при вызове предоставленного пользователем делегата, переданного методу Run, он передает этот маркер, что позволяет разорвать отмеченный выше порочный круг:

 public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
    return AsyncInfo.Run(cancellationToken => 
        DownloadStringAsyncInternal(uri, cancellationToken));
}

Лямбда-функции и анонимные методы

Метод AsyncInfo.Run упрощает работу с лямбда-функциями и анонимными методами при реализации асинхронных методов WinRT.

Например, если бы у нас не было метода DownloadStringAsyncInternal, мы могли бы реализовать его и метод DownloadStringAsync следующим образом:

 public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
    return AsyncInfo.Run(delegate(CancellationToken cancellationToken)
    {
        return DownloadStringAsyncInternal(uri, cancellationToken));
    });
}

private static async Task<string> DownloadStringAsyncInternal(
    string uri, CancellationToken cancellationToken)
{
    var response = await new HttpClient().GetAsync(
        uri, cancellationToken);
    response.EnsureSuccessStatusCode();
    return await response.Content.ReadAsStringAsync();
}

Воспользовавшись преимуществами C# и Visual Basic для написания асинхронных анонимных методов, мы можем упростить нашу реализацию, объединив два метода в один:

 public static IAsyncOperation<string> DownloadStringAsync(string uri)
{
    return AsyncInfo.Run(async delegate(CancellationToken cancellationToken)
    {
        var response = await new HttpClient().GetAsync(
            uri, cancellationToken);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync();
    });
}    

Ход выполнения

Метод AsyncInfo.Run также поддерживает отчеты о ходе выполнения через асинхронные методы WinRT.

Представьте, что нам потребовалось, чтобы метод DownloadStringAsync возвращал не IAsyncOperation<string> , а IAsyncOperationWithProgress<string,int> :

 public static IAsyncOperationWithProgress<string,int> DownloadStringAsync(string uri);

Метод DownloadStringAsync теперь может предоставлять сведения о ходе выполнения с интегральными данными. Например, объекты-получатели могут задать делегата в качестве значения свойства интерфейса Progress, чтобы получать уведомления об изменениях в ходе выполнения.

Также доступен перегруженный метод AsyncInfo.Run, который принимает объект Func<CancellationToken,IProgress<TProgress>,Task<TResult>> . Как метод AsyncInfo.Run передает делегату маркер CancellationToken, который будет отменен при вызове метода Cancel, точно так же он может передать экземпляр IProgress<TProgress> , метод Report которого активирует вызовы делегата Progress объекта-получателя. Например, если нам необходимо изменить предыдущий пример таким образом, чтобы сообщать о том, что задача выполнена на 0 % в самом начале, на 50 % — после получения ответа и на 100 % — после преобразования ответа в строку, это можно сделать следующим образом:

 public static IAsyncOperationWithProgress<string,int> DownloadStringAsync(string uri)
{
    return AsyncInfo.Run(async delegate(
        CancellationToken cancellationToken, IProgress<int> progress)
    {
        progress.Report(0);
        try
        {
            var response = await new HttpClient().GetAsync(uri, cancellationToken);
            progress.Report(50);
            response.EnsureSuccessStatusCode();
            return await response.Content.ReadAsStringAsync();
        }
        finally { progress.Report(100); }
    });
}

Приоткрывая завесу тайны

Чтобы понять, как все это работает, давайте посмотрим, как реализованы методы AsAsyncOperation и AsyncInfo.Run. Ниже приведены упрощенные реализации этих методов, не такие надежные, как в .NET 4.5. Это скорее некоторые приближения, дающие хорошее представление о принципах работы и не предназначенные для использования в производственной среде.

AsAsyncOperation

Метод AsAsyncOperation принимает объект Task<TResult> и возвращает объект IAsyncOperation<TResult> :

 public static IAsyncOperation<TResult> AsAsyncOperation<TResult>(
    this Task<TResult> source);

Для реализации этого метода нам необходимо создать тип, реализующий интерфейс IAsyncOperation<TResult> и создающий оболочку для предоставленной задачи.

 public static IAsyncOperation<TResult> AsAsyncOperation<TResult>(
    this Task<TResult> source)
{
    return new TaskAsAsyncOperationAdapter<TResult>(source);
}

internal class TaskAsAsyncOperationAdapter<TResult> : IAsyncOperation<TResult>
{
    private readonly Task<TResult> m_task;

    public TaskAsAsyncOperationAdapter(Task<TResult> task) { m_task = task; }

    ...
}

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

Во-первых, метод IAsyncInfo.Close предназначен для принудительной очистки ресурсов, использовавшихся при выполнении асинхронной операции. Поскольку у нас таких ресурсов нет (наш объект представляет всего лишь оболочку задачи), реализация будет пустой:

 public void Close() { /* NOP */ }

С помощью метода IAsyncInfo.Cancel объект-получатель асинхронной операции может запросить ее отмену. Это в чистом виде запрос, т. е. принудительное завершение операции не выполняется. Мы просто воспользуемся объектом CancellationTokenSource для сохранения запроса:

 private readonly CancellationTokenSource m_canceler =
    new CancellationTokenSource();

public void Cancel() { m_canceler.Cancel(); }

Свойство IAsyncInfo.Status возвращает объект AsyncStatus, представляющий текущее состояние операции с учетом ее асинхронного жизненного цикла. Объект может принимать одно из четырех значений: Started (начато), Completed (завершено), Error (ошибка) и Canceled (отменено). Как правило, мы можем просто делегировать этот объект соответствующему свойству Status объекта Task и сопоставить возвращенное значение TaskStatus необходимому значению AsyncStatus:

Из TaskStatus

В AsyncStatus

RanToCompletion

Completed

Faulted

Error

Canceled

Canceled

Все остальные значения при запросе на отмену

Canceled

Все остальные значения без запроса на отмену

Started

Если задача объекта Task еще не завершена и при этом получен запрос на отмену, необходимо вернуть значение Canceled, а не Started. Это означает, что хотя TaskStatus.Canceled является единственным конечным состоянием, AsyncStatus.Canceled может быть как конечным, так и неконечным состоянием, в котором для интерфейса IAsyncInfo возможен переход в состояние Canceled, а для интерфейса IAsyncInfo уже в состоянии Canceled — переход в состояние AsyncStatus.Completed или AsyncStatus.Error.

 public AsyncStatus Status
{
    get
    {
        switch (m_task.Status)
        {
            case TaskStatus.RanToCompletion: 
                return AsyncStatus.Completed;
            case TaskStatus.Faulted: 
                return AsyncStatus.Error;
            case TaskStatus.Canceled: 
                return AsyncStatus.Canceled;
            default: 
                return m_canceler.IsCancellationRequested ? 
                    AsyncStatus.Canceled : 
                    AsyncStatus.Started;
        }
    }
}

Свойство IAsyncInfo.Id возвращает идентификатор операции UInt32. Поскольку объект Task сам по себе предоставляет подобный идентификатор (в виде объекта Int32), мы можем реализовать это свойство просто путем делегирования значения свойства Task:

 public uint Id { get { return (uint)m_task.Id; } }

В WinRT свойство IAsyncInfo.ErrorCode должно возвращать объект HResult. Однако внутри среды CLR объект HResult среды WinRT сопоставляется объекту .NET Exception, предоставляя его нам через управляемую проекцию. Объект Task сам по себе предоставляет свойство Exception, поэтому мы просто можем делегировать его значение: если объект Task перейдет в состояние Faulted, мы вернем самое первое исключение (сбой объекта Task может произойти в результате нескольких исключений, например для объекта Task, возвращенного из Task.WhenAll). В противном случае возвращается значение null:

 public Exception ErrorCode
{
    get { return m_task.IsFaulted ? m_task.Exception.InnerException : null; }
}

Реализация метода IAsyncInfo завершена. Теперь нам необходимо реализовать два дополнительных члена, предоставляемых интерфейсом IAsyncOperation<TResult> : GetResults и Completed.

Объекты-получатели вызывают метод GetResults после успешного завершения операции или после возникновения исключения. В первом случае метод возвращает вычисленный результат, во втором — вызывает соответствующее исключение. Если операция завершилась в состоянии Canceled или еще не завершилась, вызывать метод GetResults недопустимо. Мы можем реализовать метод GetResults следующим образом, опираясь на метод объекта awaiter задачи GetResult, чтобы вернуть результат в случае успешного выполнения и вызвать правильное исключение в случае завершения задачи в состоянии Faulted.

 public TResult GetResults()
{
    switch (m_task.Status)
    {
        case TaskStatus.RanToCompletion:
        case TaskStatus.Faulted:
            return m_task.GetAwaiter().GetResult();
        default:
            throw new InvalidOperationException("Invalid GetResults call.");
    }
}

В конечном итоге осталось только свойство Completed. Свойство Completed представляет делегата, который необходимо вызывать по завершении операции. Если операция завершена, когда свойство Completed задано, предоставленный делегат необходимо вызвать или запланировать его вызов немедленно. Кроме того, объект-получатель может задать свойство только один раз (при попытке задать свойство несколько раз возникнет исключение). Когда операция будет завершена, реализации должны очистить ссылку на делегат, чтобы избежать утечек памяти. Большую часть функциональности можно реализовать на основе метода ContinueWith объекта Task, однако поскольку метод ContinueWith может использоваться несколько раз для одного и того же экземпляра объекта Task, нам необходимо реализовать более строгое поведение «однократного вызова»:

 private AsyncOperationCompletedHandler<TResult> m_handler;
private int m_handlerSet;

public AsyncOperationCompletedHandler<TResult> Completed
{
    get { return m_handler; }
    set
    {
        if (value == null) 
            throw new ArgumentNullException("value");
        if (Interlocked.CompareExchange(ref m_handlerSet, 1, 0) != 0)
            throw new InvalidOperationException("Handler already set.");

        m_handler = value;

        var sc = SynchronizationContext.Current;
        m_task.ContinueWith(delegate {
            var handler = m_handler;
            m_handler = null;
            if (sc == null)
                handler(this, this.Status);
            else
                sc.Post(delegate { handler(this, this.Status); }, null);
       }, CancellationToken.None, 
          TaskContinuationOptions.ExecuteSynchronously, 
          TaskScheduler.Default);
    }
}

Теперь реализация метода AsAsyncOperation завершена, и мы можем использовать в качестве метода IAsyncOperation<TResult> любой метод, возвращающий Task<TResult> .

AsyncInfo.Run

А что теперь можно сказать о более сложном методе AsyncInfo.Run?

 public static IAsyncOperation<TResult> Run<TResult>(
        Func<CancellationToken,Task<TResult>> taskProvider);

Для поддержки такой перегрузки метода Run мы можем воспользоваться уже созданным типом TaskAsAsyncOperationAdapter<TResult> , оставив все остальное без изменений. Фактически нам необходимо расширить функциональность метода, чтобы он работал в терминах Func<CancellationToken,Task<TResult>> , а не в терминах Task<TResult> . Имея такой делегат, мы можем просто вызывать его синхронно, передавая определенный ранее объект m_canceler CancellationTokenSource и сохраняя возвращенную задачу:

 internal class TaskAsAsyncOperationAdapter<TResult> : IAsyncOperation<TResult>
{
    private readonly Task<TResult> m_task;

    public TaskAsAsyncOperationAdapter(Task<TResult> task) { m_task = task; }

    public TaskAsAsyncOperationAdapter(
        Func<CancellationToken,Task<TResult>> func)
    {
        m_task = func(m_canceler.Token);
    }
    ...
}

public static class AsyncInfo
{
    public static IAsyncOperation<TResult> Run<TResult>(
        Func<CancellationToken, Task<TResult>> taskProvider)
    {
        return new TaskAsAsyncOperationAdapter<TResult>(taskProvider);
    }
    …
}

Эта реализация показывает, что ничего сверхъестественного здесь не происходит. Методы AsAsyncAction, AsAsyncOperation и AsyncInfo.Run являются всего лишь вспомогательными реализациями, избавляющими вас от необходимости написания всех этих стандартных шаблонов.

Заключение

Надеюсь, что прочитав это статью, вы получили достаточно хорошее представление о назначении методов AsAsyncAction, AsAsyncOperation и AsyncInfo.Run: они упрощают получение объекта Task или Task<TResult> и предоставляют его в виде интерфейса IAsyncAction, IAsyncOperation<TResult> , IAsyncActionWithProgress<TProgress> или IAsyncOperationWithProgress<TResult,TProgress> . Совместно с использованием ключевых слов async и await в языках C# и Visual Basic это позволяет эффективно реализовать новые асинхронные операции WinRT в управляемом коде.

Если предоставляемые функции доступны через объекты Task или Task<TResult> , для выполнения преобразований следует использовать эти встроенные возможности, а не реализовывать асинхронные интерфейсы WInRT вручную. А если требуемая функциональность в Task или Task<TResult> не поддерживается, попробуйте сначала представить ее как Task или Task<TResult> , а затем воспользуйтесь встроенными преобразованиями. Понять всю семантику, лежащую в основе реализации асинхронных интерфейсов WinRT, довольно сложно, поэтому и были реализованы соответствующие преобразования, выполняющие всю работу за вас. Аналогичная поддержка также обеспечивается при реализации асинхронных операций WinRT на языке C++ либо через базовый класс AsyncBase в библиотеке среды выполнения Windows, либо через функцию create_async в библиотеке PPL.

Удачи в работе с асинхронными операциями!

— Стивен Тауб (Stephen Toub), Visual Studio