Editor's Note: The Following MVP Monday post is by French Canadian Visual C# MVP Jerome Laban and is available in both French and English.
Programmation Asynchrone avec les Reactive Extensions (en attendant async/await)
De nos jours, avec des applications qui utilisent de plus en plus de services dans le Cloud, ou qui tout simplement exécutent de longues actions que l’utilisateur peut ressentir, il est devenu indispensable de programmer de manière asynchrone.
Mais nous, les développeurs, nous sentons à l’aise lorsque nous pensons séquentiellement. Nous préférons envoyer une requête ou exécuter une fonction, attendre la réponse, puis la traiter.
Malheureusement pour nous, il n’est plus justifiable pour une application d’attendre de manière synchrone qu’un appel se termine. Cela parce que l’utilisateur s’attend à ce que son application continue de lui répondre, ou bien qu’il est nécessaire d’effectuer un bon nombre d’opérations simultanément pour obtenir une bonne performance.
Des Frameworks hautement dépendants de l’interface utilisateur (comme Silverlight ou Silverlight pour Windows Phone) essayent de forcer la main du développeur vers la programmation asynchrone en enlevant les API synchrones. Cela laisse le développeur seul avec les patrons comme Begin/End, ou bien les simples évènements C#. Ces deux patrons ne sont pas flexibles, ne sont pas facilement composables, sont très souvent la cause des fuites mémoire, et sont relativement complexes à utiliser ou pire, à lire.
C# 5.0 et async/await
En donnant un coup d’œil rapide à un futur pas si lointain, Microsoft a pris l’importante décision d’augmenter le nouveau framework .NET 4.5 afin d’y inclure des API asynchrones. Plus particulièrement dans le cas de Windows Runtime (WinRT), il s’agit de restreindre certaines de ces API sous la seule forme asynchrone. Ces API sont basées sur la classe Task, et sont soutenues par des éléments présents dans les langages pour faciliter la programmation asynchrone.
Dans l’implémentation C# 5.0 à venir, le patron async/await tente de gérer ce problème de l’asynchronisme en faisant en sorte que du code asynchrone prenne une apparence synchrone. Cela a pour objectif de rendre la programmation asynchrone plus familière aux développeurs.
Si l’on prend cet exemple:
Le code dans GetContentFromDatabase a une apparence synchrone, mais sous le capot, ce code est en fait coupé en deux (ou plus) là où le mot clé await est utilisé.
Le compilateur applique une technique nommée « sucre syntaxique », qui est utilisée à de nombreuses reprises dans le langage C#. Le code est alors « étendu » vers une forme qui est bien moins lisible, mais qui est considéré comme de la plomberie difficile à écrire (surtout correctement) à chaque fois. Le mot clé using les itérateurs, ou plus récemment LINQ sont de très bons exemples d’utilisation de ce sucre syntaxique.
En utilisant un bon vieux Thread Pool, le code ressemble plutôt à ceci, une fois le compilateur passé:
Cet exemple est sensiblement plus complexe, et ne gère pas les exceptions proprement. Mais vous saisissez probablement le concept.
Développer en Asynchrone dès Aujourd’hui
Ceci étant, vous ne voudrez ou ne pourrez peut-être pas utiliser C# 5.0 de sitôt. Beaucoup de code utilise toujours .NET 3.5 ou même .NET 2.0, et des fonctionnalités comme async prennent du temps à être déployées sur le terrain. Alors même lorsque le Framework le propose depuis longtemps, une fonctionnalité exceptionnelle comme LINQ (depuis C# 3.0) est toujours en cours d’adoption et n’est pas très utilisée.
Les Reactive Extensions (Rx pour les intimes) offrent un Framework qui est disponible à partir de .NET 3.5 et offrent une fonctionnalité similaire à C# 5.0, mais propose une approche différente à la programmation asynchrone, plus fonctionnelle. Plus fonctionnel veut dire moins de variables pour maintenir des états, et une manière de programmer plus déclarative.
Mais ne prenez pas peur. Fonctionnel ne veut pas dire des concepts abstraits qui ne peuvent pas être utilisés par le développeur lambda. Cela veut dire (très grossièrement) que vous serez plus enclins à séparer les rôles en utilisant des fonctions plutôt que des classes.
Mais regardons du code qui ressemble aux deux précédents exemples :
Pour l’appelant (le Main), la méthode GetContentFromDatabase se comporte de la même manière qu’une Task .NET 4.5, et l’appel à Subscribe remplace la méthode ContinueWith.
Cette approche simpliste est parfaite pour un exemple. Jusque ici, vous pourriez toujours choisir d’utiliser l’exemple qui se base the la classe ThreadPool, montré précédemment dans cet article.
Un mot à propos IObservable
Un IObservable est communément considéré comme un flot de données qui peut pousser à ses souscripteurs aucune ou plusieurs valeurs, et soit une erreur ou un message de fin. C’est un modèle connu sous le nom de Push permettant d’observer la source sans bloquer de Thread. Ce modèle est opposé au modèle Pull proposé par IEnumerable qui effectue une observation bloquante de la source de données. Une très bonne vidéo avec Erik Meijer explique ces concepts sur Channel 9.
Pour se synchroniser avec le modèle de fonctionnement des Tasks de .NET 4.5, un IObservable doit fournir au maximum une valeur ou une erreur. C’est exactement ce que propose la méthode Observable.Start.
Un exemple plus réaliste
La plupart du temps, les scénarios effectuent plusieurs appels asynchrones de méthodes. Et si ils ne sont pas appelés simultanément puis rassemblés, ils sont effectués les uns après les autres.
Voici un exemple qui effectue un chainage d’appels :
L’opérateur SelectMany est un peu étrange lorsque utilisé avec la sémantique d’un IObservable qui se comporte comme une Task. Il peut alors être assimilé à l’opérateur ContinueWith. La méthode GetContentFromDatabase ne pousse qu’une seule valeur, faisant en sorte que la fonction lambda fournie à SelectMany n’est appelée qu’une seule fois.
Prendre la route Async
Un coup d’œil rapide à WinRT et la Conférence Build a fait apparaitre une règle très intéressant que Microsoft a appliquée lors de la migration version vers des API asynchrone partout dans le framework. Si un appel d’API prend de manière nominale plus de 50 millisecondes à s’exécuter, alors c’est une API asynchrone.
Cette règle est très facilement applicable à .NET 3.5 et ultérieurs, en exposant des instances de IObservable qui fournissent au plus une seule valeur, de manière à simuler des Tasks .NET 4.5.
Du coté architectural, c’est une manière de s’assurer que les consommateurs de l’API d’une couche de service ne seront moins tentés d’appeler les méthodes de manière synchrone. Tout cela dans le but de ne pas impacter la performance perçue ou réelle de l’application.
Par exemple, un service de "Favoris" implémenté dans une application pourrait ressembler à ceci, en utilisant Rx :
Toutes les opérations, incluant celle qui altèrent les données, sont exécutées de manière asynchrone. Il est toujours tentant de penser qu’une opération de sélection va prendre du temps, mais il est très facile d’oublier qu’une opération d’ajout peut prendre le même temps.
Un mot à propos de Unit : Le nom proviens des langages fonctionnels, et représente le mot clé Void, littéralement. Une limitation profonde du CLR de .NET empêche l’utilisation de System.Void comme un paramètre de type générique, et pour pouvoir fournir une valeur de retour Void, Unit a été introduit.
Conclusion
Bien plus peut être obtenu grâce à Rx, mais pour commencer, l’utiliser comme un moyen d’effectuer des appels asynchrones de méthodes simples semble être un bon moyen pour apprendre à s’en servir.
Aussi, pour les experts Rx, des raccourcis ont été pris pour expliquer les concepts dans leur forme la plus simple. Il y a beaucoup de petites techniques à connaitre pour utiliser Rx efficacement, tout particulièrement lorsqu’elles sont utilisées à la grandeur de l’application. L’omission du message Completed est un exemple de ces raccourcis.
Enfin, expliquer la richesse des Reactive Extensions n’est pas une mince affaire. Même les magiciens de l’équipe de Rx ont aussi du mal… J’espère que cet article vous aidera à plonger dedans !
Asynchronous Programming with the Reactive Extensions (while waiting for async/await)
Nowadays, with applications that use more and more services that are in the cloud, or simply perform actions that take a user noticeable time to execute, it has become vital to program in an asynchronous way.
But we, as developers, feel at home when thinking sequentially. We like to send a request or execute a method, wait for the response, and then process it.
Unfortunately for us, an application just cannot wait synchronously for a call to end anymore. Reasons can be that the user expects the application to continue responding, or because the application joins the results of multiple operations, and it is necessary to perform all these operations simultaneously for good performance.
Frameworks that are heavily UI dependent (like Silverlight or Silverlight for Windows Phone) are trying the force the developer's hand into programming asynchronously by removing all synchronous APIs. This leaves the developer alone with either the Begin/End pattern, or the plain old C# events. Both patterns are not flexible, not easily composable, often lead to memory leaks, and are just plain difficult to use or worse, to read.
C# 5.0 async/await
Taking a quick look at the not so distant future, Microsoft has taken the bold approach to augment its new .NET 4.5 to include asynchronous APIs and in the case of the Windows Runtime (WinRT), restrict some APIs to be asynchronous only. These are based on the Task class, and are backed by languages to ease asynchronous programming.
In the upcoming C# 5.0 implementation, the async/await pattern is trying to handle this asynchrony problem by making asynchronous code look synchronous. It makes asynchronous programming more "familiar" to developers.
If we take this example:
The code in GetContentFromDatabase looks synchronous, but under the hood, it's actually split in half (or more) where the await keyword is used.
The compiler is applying a technique used many times in the C# language, known as syntactic sugar. The code is expanded to a form that is less readable, but is more of a plumbing code that is painful to write – and get right – each time. The using statement, iterators and more recently LINQ are very good examples of that syntactic sugar.
Using a plain old thread pool call, the code actually looks a lot more like this, once the compiler is done:
This sample somewhat more complex, and does not properly handle exceptions. But you probably get the idea.
Asynchronous Development now
Nonetheless, you may not want or will be able to use C# 5.0 soon enough. A lot of people are still using .NET 3.5 or even .NET 2.0, and new features like async will take a while to be deployed in the field. Even when the framework has been offering it for a long time, the awesome LINQ (a C# 3.0 feature) is still being adopted and is not that widely used.
The Reactive Extensions (Rx for friends) offer a framework that is available from .NET 3.5 and functionality similar to C# 5.0, but provide a different approach to asynchronous programming, more functional. More functional is meaning fewer variables to maintain states, and a more declarative approach to programming.
But don't be scared. Functional does not mean abstract concepts that are not useful for the mainstream developer. It just means (very roughly) that you're going to be more inclined to separate your concerns using functions instead of classes.
But let's dive into some code that is similar to the two previous examples:
From the caller's perspective (the main), the GetContentFromDatabase method behaves almost the same way a .NET 4.5 Task would, and the Subscribe pretty much replaces the ContinueWith method.
But this simplistic approach works well for an example. At this point, you could still choose to use the basic ThreadPool example shown earlier in this article.
A word on IObservable
An IObservable is generally considered as a stream of data that can push to its subscribers zero or more values, and either an error or completion message. This “Push” based model that allows the observation of a data source without blocking a thread. This is opposed to the Pull model provided by IEnumerable, which performs a blocking observation of a data source. A very good video with Erik Meijer explains these concepts on Channel 9.
To match the .NET 4.5 Task model, an IObservable needs to provide at most one value, or an error, which is what the Observable.Start method is doing.
A more realistic example
Most of the time, scenarios include calls to multiple asynchronous methods. And if they're not called at the same time and joined, they're called one after the other.
Here is an example that does task chaining:
The SelectMany operator is a bit strange when it comes to the semantics of an IObservable that behaves like a Task. It can then be thought of a ContinueWith operator. The GetContentFromDatabase only pushes one value, meaning that the provided lambda for the SelectMany is only called once.
Taking the Async route
A peek at WinRT and the Build conference showed a very interesting rule used by Microsoft when moving to asynchronous API throughout the framework. If an API call nominally takes more than 50ms to execute, then it's an asynchronous API call.
This rule is easily applicable to existing .NET 3.5 and later frameworks by exposing IObservable instances that provide at most one value, as a way to simulate a .NET 4.5 Task.
Architecturally speaking, this is a way to enforce that the consumers of a service layer API will be less tempted to synchronously call methods and negatively impact the perceived or actual performance of an application.
For instance, a "favorites" service implemented in an application could look like this, using Rx:
All the operations, including ones that alter content, are executed asynchronously. It is always tempting to think a select operation will take time, but we easily forget that an Add operation could easily take the same amount of time.
A word on unit: The name comes from functional languages, and represents the void keyword, literally. A deep .NET CLR limitation prevents the use of System.Void as a generic type parameter, and to be able to provide a void return value, Unit has been introduced.
Wrap up
Much more can be achieved with Rx but for starters, using it as a way to perform asynchronous single method call seems to be a good way to learn it.
Also, a note to Rx experts, shortcuts have been taken to explain this in the most simple form, and sure there are many tips and tricks to know to use Rx effectively, particularly when it is used all across the board. The omission of the Completed event is one of them.
Finally, explaining the richness of the Reactive Extensions is a tricky task. Even the smart guys of the Rx team have a hard time doing so... I hope this quick start will help you dive into it!
Author's Bio
Jérôme Laban has over 12 years of software development experience and is currently employed as a Software Architect by nventive in Montreal, Canada. He has been working with .NET technologies since the beginning (early 2000) from harden military devices and mobile devices to large enterprise solutions. He has trained new .NET developers at Epitech in Paris for three years. His recent focus is on Windows Phone, C# latest and greatest but also the Reactive Extensions. Jérôme is a Visual C# MVP and he attempts, when the time permits, to write about his findings on his blog at http://jaylee.org/. You can follow Jérôme on twitter at twitter.com/jlaban.
Jérôme Laban a plus de 12 années d’expérience dans le développement logiciel et travaille pour nventive en tant que Architecte à Montréal, Canada. Il a travaillé avec les technologies .NET depuis leurs début (courant 2000) en passant par des applications pour ordinateurs militaires endurcis et appareils mobiles jusqu’aux larges applications d’entreprise. Il a formé de nouveaux développeurs .NET à Epitech Paris durant trois années. Il se concentre récemment sur Windows Phone, C# et ses dernières nouveautés ainsi que les Reactive Extensions. Jérôme est MVP Visual C# et tente, lorsque le temps lui permet, d’écrire à propos de ses découvertes sur son blog (http://jaylee.org/). Vous pouvez suivre Jérôme par twitter à twitter.com/jlaban.
MVP Mondays
The MVP Monday Series is created by Melissa Travers. In this series we work to provide readers with a guest post from an MVP every Monday. Melissa is a Community Program Manager for Dynamics, Excel, Office 365, Platforms and SharePoint in the United States. She has been working with MVPs since her early days as Microsoft Exchange Support Engineer when MVPs would answer all the questions in the old newsgroups before she could get to them