Exposer des tâches .NET en tant qu'opérations asynchrones WinRT

Dans le billet Plongée au cœur de WinRT et du mot clé await, nous avons abordé les nouveaux mots clés async et await en C# et Visual Basic et indiqué comment les utiliser pour les opérations asynchrones Windows Runtime (WinRT).

En vous aidant des bibliothèques de classes de base .NET (BCL), vous pouvez également utiliser ces mots clés pour développer des opérations asynchrones qui sont ensuite exposées via WinRT pour utiliser d'autres composants conçus dans d'autres langages. Dans ce billet, nous allons expliquer comment procéder. (Pour des informations globales sur l'implémentation des composants WinRT avec C# ou Visual Basic, consultez l'article sur la création de composants Windows Runtime en C# et Visual Basic.)

Commençons par examiner la forme des API asynchrones dans WinRT.

Interfaces asynchrones WinRT

WinRT comporte plusieurs interfaces associées aux opérations asynchrones. La première est IAsyncInfo, que chaque opération asynchrone WinRT valide implémente. Elle génère les fonctionnalités communes d'une opération asynchrone, notamment l'état actuel de l'opération, l'erreur de l'opération en cas d'échec, l'ID de l'opération et la capacité de l'opération à demander une annulation :

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

    void Cancel();
    void Close();
}

Outre l'implémentation de IAsyncInfo, chaque opération asynchrone WinRT valide implémente une des quatre interfaces supplémentaires suivantes : IAsyncAction, IAsyncActionWithProgress<TProgress> , IAsyncOperation<TResult> ou IAsyncOperationWithProgress<TResult,TProgress> . Ces interfaces supplémentaires permettent au client de définir un rappel qui est appelé à la fin de l'opération asynchrone, et peuvent également permettre au client d'obtenir un résultat et/ou de recevoir des rapports de progression :

 // 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 fournit une méthode GetResults même si aucun résultat n'est à renvoyer. Ceci permet d'obtenir une méthode sur une opération asynchrone en échec pour générer son exception, au lieu de forcer tous les clients à accéder à la propriété ErrorCode d'IAsyncInfo. Cela est similaire à la façon dont les types awaiter en C# et Visual Basic exposent une méthode GetResult, même si cette méthode GetResult est typée pour renvoyer void.)

Lorsque vous créez une bibliothèque WinRT, toutes les opérations asynchrones exposées publiquement dans cette bibliothèque sont fortement typées pour renvoyer une de ces quatre interfaces. Par opposition, les nouvelles opérations asynchrones exposées dans les bibliothèques .NET suivent le modèle asynchrone basé sur les tâches (TAP (Task-based Asynchronous Pattern)), renvoyant Task ou Task<TResult> , le premier pour les opérations qui ne renvoient pas de résultat, le deuxième pour les opérations qui en renvoient.

Task et Task<TResult> n'implémentent pas ces interfaces WinRT. Common Language Runtime (CLR) ne comble pas non plus les différences (comme il le fait pour certains types, par exemple le type WinRT Windows.Foundation.Uri et le type BCL System.Uri). Nous devons à la place convertir explicitement un monde en un autre. Dans le billet Plongée au cœur de WinRT et du mot clé await, nous avons vu comment la méthode d'extension AsTask dans BCL fournit un mécanisme explicite, similaire à une conversion de type, pour convertir les interfaces asynchrones WinRT en tâches .NET. BCL prend également en charge l'autre sens, avec des méthodes de conversion des tâches .NET en interfaces asynchrones WinRT.

Conversion avec AsAsyncAction et AsAsyncOperation

Dans le cadre de ce billet, supposons que nous avons une méthode asynchrone .NET DownloadStringAsyncInternal. Nous la transmettons à l'URL d'une page Web. La méthode est téléchargée de façon asynchrone et renvoie le contenu de cette page sous forme de chaîne :

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

La manière dont cette méthode est implémentée importe peu. En revanche, notre objectif est de l'englober en tant qu'opération asynchrone WinRT, c'est-à-dire en tant que méthode qui renvoie une des quatre interfaces précédemment mentionnées. Comme notre opération a un résultat (une chaîne) et comme elle ne prend pas en charge les rapports de progression, notre opération asynchrone WinRT renvoie IAsyncOperation<string>  :

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

Pour implémenter cette méthode, nous pouvons appeler la méthode DownloadStringAsyncInternal pour obtenir Task<string> . Nous devons ensuite convertir cette tâche en IAsyncOperation<string> requis… mais comment ?

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

Pour résoudre cette difficulté, l'assembly System.Runtime.WindowsRuntime.dll dans .NET 4.5 inclut des méthodes d'extension pour Task et Task<TResult> qui fournissent les conversions nécessaires :

 // 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);
    ...
}

Ces méthodes renvoient une nouvelle instance IAsyncAction ou IAsyncOperation<TResult> qui englobe les instances Task ou Task<TResult> , respectivement (comme Task<TResult> est un dérivé de Task, ces deux méthodes sont disponibles pour Task<TResult> , bien qu'il soit relativement rare d'utiliser AsAsyncAction avec une méthode asynchrone renvoyant un résultat). Logiquement, vous pouvez penser que ces opérations sont des conversions de types asynchrones explicites, ou d'un point de vue des modèles de conception, des adaptateurs. Elles retournent une instance qui représente la tâche sous-jacente, mais qui expose la surface d'exposition requise pour WinRT. Avec ces méthodes d'extension, nous pouvons réaliser notre implémentation DownloadStringAsync :

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

Nous pouvons également l'écrire plus succinctement, en mettant en évidence la conversion de type de l'opération :

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

DownloadStringAsyncInternal est appelé avant AsAsyncOperation. Cela signifie que l'appel asynchrone à DownloadStringAsyncInternal doit revenir rapidement pour s'assurer que la méthode wrapper DownloadStringAsync est réactive. Si, pour une raison quelconque, vous craignez que la tâche asynchrone que vous effectuez prenne trop de temps, ou si vous voulez explicitement décharger l'invocation vers un pool de thread pour une autre raison, vous pouvez utiliser Task.Run, puis appeler AsAsyncOperation sur sa tâche renvoyée :

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

Flexibilité accrue avec AsyncInfo.Run

Ces méthodes d'extension intégrées AsAsyncAction et AsAsyncOperation sont idéales pour les conversions simples de Task en IAsyncAction et de Task<TResult> en IAsyncOperation<TResult> . Mais qu'en est-il des conversions plus avancées ?

System.Runtime.WindowsRuntime.dll contient un autre type qui offre une flexibilité accrue : AsyncInfo, dans l'espace de noms System.Runtime.InteropServices.WindowsRuntime. AsyncInfo expose quatre surcharges d'une méthode Run statique, une pour chacune des quatre interfaces asynchrones 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);
}

Les méthodes AsAsyncAction et AsAsyncOperation que nous avons déjà examinées acceptent une tâche Task comme argument. Par opposition, ces méthodes Run acceptent un délégué de fonction qui renvoie une tâche Task, et cette différence entre Task et Func<…,Task> est suffisante pour nous conférer la flexibilité supplémentaire dont nous avons besoin pour les opérations plus avancées.

Vous pouvez logiquement considérer AsAsyncAction et AsAsyncOperation comme de simples applications d'assistance sur l'élément AsyncInfo.Run le plus avancé :

 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);
}

Ce n'est pas exactement comme cela qu'ils sont implémentés dans .NET 4.5, mais fonctionnellement, leur comportement est le même. Cela aide par conséquent d'y penser comme tel pour faire la différence entre une prise en charge basique et avancée. Si votre cas est simple, utilisez AsAsyncAction et AsAsyncOperation, mais dans plusieurs cas avancés, AsyncInfo.Run est le plus approprié.

Annulation

AsyncInfo.Run permet de prendre en charge l'annulation avec les méthodes asynchrones WinRT.

Pour reprendre notre exemple de téléchargement, supposons que nous ayons une autre surcharge DownloadStringAsyncInternal qui accepte un jeton CancellationToken :

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

CancellationToken est de type .NET Framework et prend en charge l'annulation coopérative de manière composable. Vous pouvez transmettre un seul jeton à n'importe quel nombre d'appels de méthode, puis lorsqu'une annulation est demandée pour ce jeton (via l'élément CancellationTokenSource qui a créé le jeton), la demande d'annulation est alors visible à toutes ces opérations. Cette approche diffère légèrement de celle utilisée par la méthode asynchrone WinRT, qui consiste à faire en sorte que chaque élément IAsyncInfo individuel expose sa propre méthode Cancel. Ainsi, comment procéder pour qu'une annulation soit demandée pour un appel à la méthode Cancel sur l'élément IAsyncOperation<string> d'un jeton CancellationToken qui est transmis à DownloadStringAsyncInternal ? AsAsyncOperation ne fonctionne pas dans ce cas :

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

Pour savoir quand l'annulation est demandée sur l'élément IAsyncOperation<string> , cette instance doit avertir un écouteur que sa méthode Cancel a été appelée, par exemple en demandant l'annulation d'un jeton CancellationToken que nous transmettrions à DownloadStringAsyncInternal. Mais dans une situation paradoxale classique, nous n'obtenons pas l'élément Task<string> sur lequel appeler AsAsyncOperation tant que nous n'avons pas appelé DownloadStringAsyncInternal, auquel point nous aurions déjà dû fournir le jeton CancellationToken que nous souhaitions que l'opération AsAsyncOperation fournisse.

Il existe plusieurs façons de résoudre ce dilemme, notamment la solution employée par AsyncInfo.Run. La méthode Run est responsable de la construction de l'élément IAsyncOperation<string> , et elle crée cette instance pour demander l'annulation d'un jeton CancellationToken qui est également créé lorsque la méthode Cancel de l'opération asynchrone est appelée. Ensuite, lors de l'appel au délégué fourni par l'utilisateur transmis à Run, il transmet ce jeton, évitant le cycle précédemment examiné :

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

Fonctions lambda et méthodes anonymes

AsyncInfo.Run simplifie l'utilisation des fonctions lambda et des méthodes anonymes pour implémenter les méthodes asynchrones WinRT.

Par exemple, si nous n'avions pas encore la méthode DownloadStringAsyncInternal, nous pouvons l'implémenter et DownloadStringAsync comme ceci :

 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();
}

En tirant parti de la prise en charge de C# et de Visual Basic pour l'écriture de méthodes anonymes asynchrones, nous pouvons simplifier notre implémentation en combinant ces deux méthodes dans une :

 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();
    });
}    

Progression

AsyncInfo.Run prend également en charge les rapports de progression par le biais des méthodes asynchrones WinRT.

Au lieu que notre méthode DownloadStringAsync renvoie un élément IAsyncOperation<string> , imaginez que nous souhaitions qu'elle renvoie à la place un élément IAsyncOperationWithProgress<string,int>  :

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

DownloadStringAsync peut maintenant fournir des mises à jour de progression contenant des données intégrales, de sorte que les clients peuvent définir un délégué comme propriété Progress de l'interface pour recevoir des notifications de l'évolution de la progression.

AsyncInfo.Run fournit une surcharge qui accepte un élément Func<CancellationToken,IProgress<TProgress>,Task<TResult>> . Tout comme AsyncInfo.Run transmet au délégué un jeton CancellationToken pour lequel l'annulation sera demandée lors de l'appel de la méthode Cancel, il peut également passer une instance IProgress<TProgress> dont la méthode Report déclenche des invocations du délégué Progress du client. Par exemple, pour modifier notre exemple précédent de façon à signaler 0 % de progression au début, 50 % de progression lorsque la réponse revient et 100 % de progression après l'analyse de la réponse en chaîne, cela peut ressembler à ceci :

 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); }
    });
}

Vision en coulisses

Pour savoir comment tout cela fonctionne, regardons une implémentation des méthodes AsAsyncOperation et AsyncInfo.Run. Il ne s'agit pas des mêmes implémentations que dans .NET 4.5, et elles ne sont pas aussi robustes. Il s'agit d'approximations qui donnent une bonne idée de la façon dont les choses fonctionnent en coulisses, et ne sont pas destinées à être utilisées dans un environnement de production.

AsAsyncOperation

La méthode AsAsyncOperation prend un élément Task<TResult> et renvoie un élément IAsyncOperation<TResult>  :

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

Pour implémenter cette méthode, nous devons créer un type qui implémente IAsyncOperation<TResult> et qui englobe la tâche fournie.

 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; }

    ...
}

Chacune des implémentations des méthodes d'interface sur ce type déléguera aux fonctionnalités de la tâche englobée. Commençons par les membres les plus faciles.

D'abord, la méthode IAsyncInfo.Close est censée nettoyer rapidement les ressources utilisées par l'opération asynchrone terminée. Comme nous ne disposons pas de telles ressources (notre objet se contente d'englober une tâche), notre implémentation est vide :

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

Avec la méthode IAsyncInfo.Cancel, un client de l'opération asynchrone peut demander son annulation. Il s'agit purement d'une requête et ne force en aucun cas l'opération à se terminer. Nous utilisons simplement un élément CancellationTokenSource pour stocker le fait qu'une requête a eu lieu :

 private readonly CancellationTokenSource m_canceler =
    new CancellationTokenSource();

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

La propriété IAsyncInfo.Status renvoie un élément AsyncStatus pour représenter l'état actuel de l'opération par rapport à son cycle de vie asynchrone. Il peut s'agir d'une des quatre valeurs suivantes : Started, Completed, Error ou Canceled. À quelques exceptions près, nous pouvons simplement déléguer à la propriété Status de la tâche Task sous-jacente et mapper son état TaskStatus renvoyé à l'état AsyncStatus requis :

De TaskStatus

À AsyncStatus

RanToCompletion

Completed

Faulted

Error

Canceled

Canceled

Toutes les autres valeurs et l'annulation ont été demandées

Canceled

Les autres valeurs et l'annulation n'ont pas été demandées

Started

Si la tâche Task n'est pas encore terminée et que l'annulation a été demandée, nous devons renvoyer Canceled au lieu de Started. Cela signifie que, bien que TaskStatus.Canceled n'est qu'un état terminal, AsyncStatus.Canceled peut être un état terminal ou non, en ce sens qu'il est possible pour un élément IAsyncInfo de se terminer dans l'état Canceled ou, pour un élément IAsyncInfo dans l'état Canceled de passer à AsyncStatus.Completed ou à 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;
        }
    }
}

La propriété IAsyncInfo.Id renvoie un identifiant UInt32 pour l'opération. Que la tâche Task elle-même expose déjà cet identifiant (comme Int32), nous pouvons implémenter cette propriété simplement en déléguant via la propriété Task sous-jacente :

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

WinRT définit la propriété IAsyncInfo.ErrorCode de façon à renvoyer un HResult. Mais le CLR mappe en interne le HResult WinRT à une exception Exception .NET, le faisant ainsi apparaître par le biais d'une projection gérée. Task lui-même expose une propriété Exception afin de simplement déléguer par son intermédiaire : si la tâche Task s'est terminée dans l'état Faulted, nous renvoyons sa première exception (une tâche Task peut rencontrer une erreur en raison de plusieurs exceptions, comme lorsque la tâche Task est renvoyée de Task.WhenAll). Sinon, null est renvoyé :

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

C'est tout en ce qui concerne l'implémentation de IAsyncInfo. Nous devons maintenant implémenter les deux membres supplémentaires fournis par IAsyncOperation<TResult>  : GetResults et Completed.

Les clients appellent GetResults lorsque l'opération s'est terminée avec succès ou lorsqu'une exception s'est produite. Dans le premier cas, la méthode renvoie le résultat calculé et dans le deuxième cas, elle génère l'exception appropriée. Si l'opération s'est terminée dans l'état Canceled ou si elle ne s'est pas encore terminée, il n'est pas possible d'appeler GetResults. Ainsi, nous pouvons implémenter GetResults comme suit, en nous appuyant sur la méthode GetResult de l'awaiter de la tâche pour renvoyer le résultat en cas de réussite ou pour propager l'exception appropriée si la tâche s'est terminée dans l'état 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.");
    }
}

Enfin, nous avons la propriété Completed. La propriété Completed représente un délégué qui doit être appelé au terme de l'opération. Si l'opération est déjà terminée lorsque la propriété Completed est définie, le délégué fourni doit être appelé ou planifié immédiatement. En outre, un client ne peut définir la propriété qu'une seule fois (les tentatives visant à la définir plusieurs fois provoquent des exceptions). Et au terme de l'opération, les implémentations doivent supprimer la référence au délégué afin d'éviter toute fuite de mémoire. Nous pouvons nous appuyer sur la méthode ContinueWith de Task pour implémenter une partie de ce comportement, mais comme ContinueWith peut être utilisée plusieurs fois sur la même instance Task, nous devons implémenter manuellement le comportement plus restrictif de « définition unique » :

 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);
    }
}

Ainsi, notre implémentation de AsAsyncOperation est terminée et nous pouvons utiliser n'importe quelle méthode renvoyant Task<TResult> comme méthode IAsyncOperation<TResult> .

AsyncInfo.Run

Maintenant, qu'en est-il de la méthode AsyncInfo.Run plus avancée ?

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

Pour prendre en charge cette surcharge Run, nous pouvons utiliser le type TaskAsAsyncOperationAdapter<TResult> que nous venons de créer, en laissant toute notre implémentation existante intacte. En fait, nous avons simplement besoin d'ajouter la possibilité de fonctionner en termes de Func<CancellationToken,Task<TResult>> au lieu de Task<TResult> . Lorsqu'un tel délégué est fourni, nous pouvons simplement l'appeler de façon synchrone, en passant le CancellationTokenSource de m_canceler que nous avons défini précédemment et en stockant la tâche renvoyée :

 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);
    }
    …
}

Cette implémentation met en évidence qu'aucune magie n'entre en jeu ici. AsAsyncAction, AsAsyncOperation et AsyncInfo.Run sont simplement des implémentations d'applications d'assistance qui vous évitent d'écrire tout ce code réutilisable vous-même.

Conclusion

À ce stade, nous espérons que vous comprenez bien le rôle des méthodes AsAsyncAction, AsAsyncOperation et AsyncInfo.Run : elles permettent de prendre facilement une instance Task ou Task<TResult> et de l'exposer en tant qu'interface IAsyncAction, IAsyncOperation<TResult> , IAsyncActionWithProgress<TProgress> ou IAsyncOperationWithProgress<TResult,TProgress> . Avec les mots clés async et await en C# et Visual Basic, cela rend très facile l'implémentation de nouvelles opérations asynchrones WinRT dans du code géré.

Tant que les fonctionnalités que vous exposez sont disponibles en tant que Task ou Task<TResult> , vous devez vous appuyer sur ces fonctionnalités intégrées pour réaliser les conversions à votre place au lieu d'implémenter manuellement les interfaces asynchrones WInRT. Et si les fonctionnalités que vous essayez d'exposer ne sont pas encore disponibles en tant que Task ou Task<TResult> , essayez de les exposer en tant que Task ou Task<TResult> d'abord, puis appuyez-vous sur les conversions intégrées. Il peut sembler quelque peu difficile de maîtriser toute la sémantique qui entoure l'implémentation des interfaces asynchrones WinRT, et c'est pour cela que ces conversions sont là pour le faire à votre place. Une prise en charge similaire est proposée si vous implémentez des opérations asynchrones WinRT en C++, que ce soit via la classe AsyncBase de base dans la Bibliothèque Windows Runtime ou via la fonction create_async dans la Bibliothèque de modèles parallèles.

Joyeuse asynchronisation !

--Stephen Toub, Visual Studio