Des applications toujours rapides et fluides grâce à l'asynchronisme dans Windows Runtime

Blog des développeurs d'applications Windows 8

Indications sur la conception d'applications de style Metro pour Windows 8, par l'équipe d'ingénierie de Windows 8

Des applications toujours rapides et fluides grâce à l'asynchronisme dans Windows Runtime

  • Comments 0

Notre comportement à nous, êtres humains, n'est pas naturellement synchrone, ce qui a un effet direct sur la façon dont nous voulons que les applications nous répondent. Windows Runtime (WinRT) a adopté cet asynchronisme comme composant de premier ordre dans la conception d'applications de style Metro rapides et fluides. Si vous concevez une application de style Metro, vous êtes amené à un certain moment à écrire du code asynchrone. Dans ce billet de blog, nous expliquons pourquoi la programmation asynchrone occupe une place si importante dans WinRT. Nous vous indiquons également rapidement comment l'utiliser dans vos applications et donnons quelques données sur son fonctionnement.

Les applications rapides et fluides doivent être réactives

Combien de fois avez-vous utilisé une application Windows qui tout à coup ne répond plus et qui devient grisée, vous présentant un symbole de cercle tournant ? À chaque fois, cela semble arriver au pire moment possible. Encore pire, vous pouvez vous retrouver à perdre tout votre travail lorsque cela se produit.

Les utilisateurs attendent des applications qu'elles répondent à toutes les interactions. Lorsqu'ils utilisent leur application d'actualités préférée, ils veulent pouvoir ajouter des flux d'actualités, lire des articles d'informations, en enregistrer certains, etc. Ils doivent être capables de réaliser toutes ces opérations même lorsque l'application est en train de récupérer les derniers articles d'Internet.

Cela devient tout particulièrement important lorsqu'un utilisateur interagit avec votre application de façon tactile. Lorsque l'application ne « colle plus au doigt », les utilisateurs le remarquent bien. Même les problèmes de performances mineurs peuvent impacter négativement l'expérience utilisateur et entraver ce sentiment de rapidité et de fluidité.

Une application n'est ni rapide, ni fluide si elle ne répond plus aux actions des utilisateurs. Alors, pourquoi les applications ne répondent-elles plus ? Une des principales causes est que l'application est synchrone. Elle attend qu'une autre opération se termine (par exemple l'obtention de données à partir d'Internet) et ne peut pas répondre à votre action.

De nombreuses applications modernes se connectent aux réseaux sociaux, stockent des données dans le cloud, utilisent des fichiers sur le disque dur, communiquent avec d'autres gadgets et appareils, etc. Les latences de certaines de ces sources sont imprévisibles, ce qui rend difficile la création d'applications rapides et fluides. Sauf si elle sont créées correctement, les applications passent plus de temps à attendre la réponse de l'environnement extérieur qu'à répondre aux besoins de l'utilisateur.

Prendre en charge cet environnement connecté a été un de nos principes de base lorsque nous avons commencé à concevoir des API pour Windows Runtime (WinRT). Nous avons compris qu'il était important de fournir une surface d'API puissante qui mène par défaut à des applications rapides et fluides.

Pour atteindre ces objectifs, nous avons fait en sorte que de nombreuses API potentiellement liées aux entrées-sorties (E/S) soient asynchrones dans Windows Runtime. Ce sont elles qui sont les plus susceptibles de dégrader de façon visible les performances si elles sont écrites de façon synchrone (par exemple, elles peuvent prendre plus de 50 millisecondes pour s'exécuter). Cette approche asynchrone des API vous permet d'écrire du code à la fois rapide et fluide par défaut et met en avant l'importance de la réactivité des applications dans le développement d'applications de style Metro.

Pour ceux d'entre vous qui ne connaissent pas les modèles asynchrones, pensez à l'asynchronisme dans WinRT comme s'il s'agissait de donner à quelqu'un un numéro à rappeler par téléphone. Vous donnez à cette personne le numéro à rappeler, vous raccrochez, puis vous continuez un autre travail que vous devez faire. Lorsque la personne est prête à vous parler, elle peut vous rappeler à l'aide du numéro que vous lui avez donné. Ceci est l'essence même du fonctionnement de l'asynchronisme dans WinRT.

Pour mieux comprendre la nature asynchrone de WinRT, nous allons d'abord examiner la façon dont vous pouvez utiliser simplement ces API asynchrones. Ensuite, nous étudierons les primitives asynchrones introduites par WinRT (sur lesquelles reposent les nouvelles fonctionnalités de langage), ainsi que leur fonctionnement.

Comment l'utiliser : nouvelles améliorations de développement pour l'asynchronisme

Dans le passé, écrire du code asynchrone était difficile. Nos équipes chargées de la création d'outils pour les applications de style Metro Windows 8 ont réalisé des avancées importantes pour résoudre ce problème dans les récentes versions. Dans .NET Framework 4.0, nous avons introduit la Bibliothèque parallèle de tâches. Ensuite, le mot-clé « await » a été introduit dans C# et Visual Basic. C++ a investi dans des technologies, telles que la Bibliothèque de modèles parallèles (PPL). Quant à JavaScript, il dispose d'outils formidables, tels que « Promises ».

Chacune de ces technologies vous propose une manière simple d'écrire du code asynchrone. L'introduction d'un modèle asynchrone standard utilisé par toutes ces technologies vous permet de les utiliser lorsque vous créez des applications de style Metro, quel que soit le langage de programmation que vous utilisez.

Dans le cas de votre application d'actualités préférée, le code qui demeure réactif tout en récupérant les articles d'actualités est relativement simple à l'aide de l'API Windows.Web.Syndication.SyndicationClient. L'appel à RetrieveFeedAsync revient immédiatement. C'est une bonne chose, car les résultats peuvent prendre du temps pour revenir d'Internet. Dans l'intervalle, l'interface utilisateur continue à interagir avec l'utilisateur.

Le code mis en surbrillance en jaune s'exécute après la fin de l'appel à RetrieveFeedAsync. Vous n'avez pas besoin de penser au mécanisme « reprenez là où vous vous étiez arrêté ». Le code est écrit simplement comme s'il était synchrone.

Code JavaScript récupérant un flux RSS de façon asynchrone :
var title;
var feedUri = new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

client.retrieveFeedAsync(feedUri).done(function(feed) {
title = feed.title.text;
});
 
Code C# récupérant un flux RSS de façon asynchrone :
var feedUri = new Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();
var feed = await client.RetrieveFeedAsync(feedUri);
var title = feed.Title.Text;

Code VB récupérant un flux RSS de façon asynchrone :
Dim feedUri = New Uri("http://www.devhawk.com/rss.xml");
Dim client = New Windows.Web.Syndication.SyndicationClient();
Dim feed = Await client.RetrieveFeedAsync(feedUri);
Dim title = feed.Title.Text;
 

Code C++ récupérant un flux RSS de façon asynchrone :
auto feedUri = ref new Windows::Foundation::Uri("http://www.devhawk.com/rss.xml");
auto client = ref new Windows::Web::Syndication::SyndicationClient();

task<SyndicationFeed^> retrieveTask(client->RetrieveFeedAsync(feedUri));
retrieveTask.then([this] (SyndicationFeed^ feed) {
this->title = feed->Title->Text;
});

 

Il n'est fait aucune mention des threads, changements de contexte, répartiteurs, etc. Ces nouvelles améliorations de développement portant sur l'écriture de code asynchrone s'occuperont de cela pour vous. Le code qui suit l'appel de l'API asynchrone est automatiquement rappelé dans le même contexte que celui dans lequel l'appel d'origine a été fait. En d'autres termes, vous pouvez mettre à jour l'interface utilisateur avec les résultats sans vous soucier d'être renvoyé au thread de l'interface utilisateur.

Comment l'utiliser : gestion des erreurs

Évidemment, les appels des API peuvent échouer (par exemple en cas de perte du réseau lors de la récupération de flux RSS). Pour qu'ils soient réellement fiables, nous devons être capables de résister en présence d'erreurs. Utiliser la fonctionnalité asynchrone dans les langages permet de simplifier la gestion des erreurs. Pour ce faire, la méthode appropriée dépend du langage utilisé.

Pour JavaScript, nous vous recommandons de toujours terminer vos chaînes « Promise » avec done. Cela permet de s'assurer que les exceptions capturées dans la chaîne « Promise » sont visibles pour le développeur (c'est-à-dire signalées dans le gestionnaire d'erreurs, ou levées). Done a la même signature que then. Par conséquent, pour gérer les erreurs, il vous suffit de transmettre un délégué d'erreur à done():

 

var title;
var feedUri = new Windows.Foundation.Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

client.retrieveFeedAsync(feedUri).done(function(feed) {
title = feed.title.text;
}, function(error) {
console.log('an error occurred: ');
console.dir(error);
}
);

Pour gérer l'exception en C# ou Visual Basic, utilisez un bloc try/catch comme vous le faites aujourd'hui avec le code synchrone :

 

var title;
var feedUri = new Uri("http://www.devhawk.com/rss.xml");
var client = new Windows.Web.Syndication.SyndicationClient();

try
{

var feed = await client.RetrieveFeedAsync(feedUri);
title = feed.Title.Text;
}
catch (Exception ex)
{
// An exception occurred from the async operation
}

La méthode de gestion des erreurs asynchrones la plus répandue à utiliser en C++ consiste à utiliser une autre continuation basée sur les tâches qui lève les exceptions (vous trouverez d'autres méthodes de gestion des erreurs en C++ à la rubrique Programmation asynchrone en C++ dans le Centre de développement) :

auto feedUri = ref new Windows::Foundation::Uri("http://www.devhawk.com/rss.xml");
auto client = ref new Windows::Web::Syndication::SyndicationClient();

task<SyndicationFeed^> retrieveTask(client->RetrieveFeedAsync(feedUri));
retrieveTask.then([this] (SyndicationFeed^ feed) {
this->title = feed->Title->Text;
}).then([] (task<void> t) {
try
{
t.get();
// .get() didn't throw, so we succeeded
}
catch (Exception^ ex)
{
// An error occurred
}
})
;

Pour en savoir plus sur l'utilisation de ces améliorations asynchrones (notamment pour voir plus de scénarios, par exemple sur l'annulation et la progression) consultez les rubriques suivantes :

Comment ça marche : primitives asynchrones WinRT

L'asynchronisme est très puissant, mais il peut sembler que beaucoup de la magie qui se produit ici se fait en votre nom. Pour démystifier ce qui se passe, regardons d'un peu plus près comment l'asynchronisme fonctionne dans WinRT. Nous commençons par enquêter sur les primitives sur lesquelles le modèle repose. La plupart d'entre vous n'auront pas besoin d'utiliser ces primitives (si vous êtes néanmoins amené à les utiliser, nous aimerions avoir votre avis).

En commençant par le code C#, regardons le type de retour réel de RetrieveFeedAsync (affiché ici dans C# Intellisense) :

 Intellisense apparaît pour la méthode RetrieveFeedAsync
 Figure 1. Intellisense apparaît pour la méthode RetrieveFeedAsync

RetrieveFeedAsync renvoie une interface IAsyncOperationWithProgress. IAsyncOperationWithProgress est l'une des 5 interfaces qui définissent la surface principale de programmation asynchrone pour WinRT : IAsyncInfo, IAsyncAction, IAsyncActionWithProgress, IAsyncOperation et IAsyncOperationWithProgress.

L'interface principale sur laquelle le modèle asynchrone WinRT repose est IAsyncInfo. Cette interface principale définit les propriétés d'une opération asynchrone, telles que l'état actuel, la possibilité d'annuler l'opération, l'erreur d'une opération ayant échouée, etc.

Comme nous l'avons mentionné précédemment, les opérations asynchrones peuvent renvoyer des résultats. Les opérations asynchrones dans WinRT peuvent également signaler la progression de leur exécution. Les 4 autres interfaces asynchrones ci-dessus (IAsyncAction, IAsyncActionWithProgress, IAsyncOperation et IAsyncOperationWithProgress) définissent différentes combinaisons de résultats et de progressions.

 

Tableau montrant les différentes combinaisons de résultats et de progressions   
Figure 2. Différentes combinaisons de résultats et de progressions

Le fait de fournir des primitives asynchrones principales dans Windows Runtime permet à C#, à Visual Basic, à C++ et à JavaScript de projeter des opérations asynchrones WinRT d'une façon qui vous est familière.

Remarque : du passage du démarrage à froid au démarrage à chaud

Dans la version Windows 8 Developer Preview sortie lors de la conférence //build, toutes les opérations asynchrones dans WinRT ont été démarrées à froid. Elles ne se sont pas exécutées immédiatement. Elles devaient être explicitement démarrées par le développeur ou démarrées implicitement à l'aide des fonctionnalités asynchrones en C#, Visual Basic, JavaScript ou C++. Nous avons reçu des commentaires de plusieurs sources indiquant que cela n'était pas intuitif et pouvait engendrer du désagrément et de la confusion.

Dans la version Windows 8 Consumer Preview, vous noterez que nous avons supprimé la méthode Start() des primitives asynchrones WinRT. Nos opérations asynchrones démarrent maintenant toutes à chaud. Ainsi, la méthode asynchrone a lancé l'opération avant de la renvoyer à l'appelant.

Ce n'est qu'un des nombreux changements que vous trouverez dans la version Consumer Preview, dans laquelle nous avons amélioré la plateforme des développeurs en fonction des remarquables commentaires que vous nous avez fait parvenir.

Comment ça marche : résultats, annulation et erreurs avec les primitives asynchrones

Une opération asynchrone démarre dans l'état Démarré et peut progresser vers l'un des trois autres états : Annulé, Terminé et Erreur. L'état actuel d'une opération asynchrone est reflété dans la propriété « Status » (État) de l'opération asynchrone (représentée par le type d'énumération AsyncStatus).

Avec une opération asynchrone, l'association d'un gestionnaire Terminé est la première action que nous opérons. À l'intérieur du gestionnaire Terminé, nous pouvons obtenir les résultats et les utiliser.

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
op = client.RetrieveFeedAsync(feedUri);

op.Completed = (info, status) =>
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
};

Vous pouvez parfois avoir besoin d'annuler une opération. Pour ce faire, vous pouvez appeler la méthode Cancel sur l'opération asynchrone.

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
op = client.RetrieveFeedAsync(feedUri);
op.Cancel();

Le gestionnaire Terminé est toujours appelé pour une opération asynchrone, qu'elle soit terminée, annulée ou qu'elle ait provoqué une erreur. Appelez GetResults uniquement lorsque l'opération asynchrone est terminée (ne l'appelez pas, par exemple, lorsque l'opération est annulée ou se termine par une erreur). Vous pouvez le savoir en inspectant le paramètre d'état.

IAsyncOperationWithProgress<SyndicationFeed, RetrievalProgress> op;
op = client.RetrieveFeedAsync(feedUri);
op.Cancel();

op.Completed = (info, status) =>
{
if (status == AsyncStatus.Completed)
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
}
else if (status == AsyncStatus.Canceled)
{
// Operation canceled
}
};

Ce code n'est toujours pas fiable en cas d'échec de l'opération. Tout comme l'annulation, le rapport d'erreurs est pris en charge par le biais de la même opération asynchrone. En plus d'utiliser AsyncStatus pour faire la différence entre Terminé et Annulé, nous l'utilisons également pour déterminer l'échec d'une opération asynchrone (c'est-à-dire AsyncStatus.Error). Vous trouverez le code d'échec spécifique dans la propriété ErrorCode de l'opération asynchrone.

op.Completed = (info, status) =>
{
if (status == AsyncStatus.Completed)
{
SyndicationFeed feed = info.GetResults();
UpdateAppWithFeed(feed);
}
else if (status == AsyncStatus.Canceled)
{
// Operation canceled
}
else if (status == AsyncStatus.Error)
{
// Error occurred, Report error
}
};

Comment ça marche : rapport de progression avec les primitives asynchrones

Certaines opérations asynchrones WinRT fournissent des notifications de progression lorsqu'elles s'exécutent. Vous pouvez utiliser ces notifications pour signaler à l'utilisateur la progression en cours de l'opération asynchrone.

Dans WinRT, le rapport d'erreurs est géré par le biais des interfaces IAsyncActionWithProgress<TProgress> et IAsyncOperationWithProgress<TResult, TProgress>. Chaque interface contient un événement Progress que vous pouvez utiliser pour obtenir les rapports de progression auprès de l'opération asynchrone.

Le modèle de programmation d'utilisation de ces données est presque identique à celui de l'utilisation de l'événement Terminé. Dans notre exemple de récupération de flux de syndication, nous pouvons rapporter le nombre d'octets récupérés par rapport au nombre total :

 

op = client.RetrieveFeedAsync(feedUri);

float percentComplete = 0;
op.Progress = (info, progress) =>
{
percentComplete = progress.BytesRetrieved /
(float)progress.TotalBytesToRetrieve;
};

Dans ce cas, le deuxième paramètre (progress) est de type RetrievalProgress. Ce type contient deux paramètres (bytesRetrieved et totalBytesToRetrieve) qui sont utilisés pour rapporter notre progression constante.

Pour en savoir plus (notamment sur la programmation asynchrone dans WinRT ou sur l'asynchronisme en général), vous pouvez consulter :

Pour conclure

Ce qui est intéressant au sujet d'une telle prédominance de l'asynchronisme dans WinRT, c'est son impact sur la façon dont vous structurez votre code et architecturez votre application. Vous pouvez maintenant penser à votre application d'une façon plus faiblement connectée. Si vous avez des commentaires sur le sujet, n'hésitez pas à nous en faire part ! Laissez un commentaire ici ou sur les forums du Centre de développement.

Nous voulons que vos clients aiment vos applications Windows 8. Nous voulons que ces applications s'animent et restent animées. Et nous voulons que vous puissiez créer ces applications plus facilement. Nous voulons que vous puissiez facilement vous connecter aux réseaux sociaux, stocker des données dans le cloud, utiliser des fichiers sur le disque dur, communiquer avec d'autres gadgets et périphériques, etc. tout en procurant à vos clients une expérience utilisateur extraordinaire.

 

--Jason Olson

Chef de projet, Windows

  • Loading...
Leave a Comment
  • Please add 3 and 1 and type the answer here:
  • Post