Tag Archives: C#

Injecter automatiquement des fakes dans une test fixture avec FakeItEasy

Aujourd’hui j’aimerais partager avec vous une fonctionnalité sympa de FakeItEasy que j’ai découverte récemment.

Quand on écrit des tests unitaires pour une classe qui a des dépendances, on a généralement besoin de créer des objets factices (fakes) pour les dépendances et de les injecter manuellement dans la classe testée (SUT, System Under Test), ou d’utiliser un conteneur IoC pour enregistrer les dépendances factices et construire le SUT. C’est un peu laborieux, et il y a quelques mois j’avais créé une extension Unity pour créer automatiquement les fakes et rendre tout ça plus facile. Mais maintenant je réalise que FakeItEasy offre une solution encore meilleure : déclarer simplement les dépendances et le SUT comme des propriétés de la fixture, et appeler Fake.InitializeFixture sur la fixture pour les initialiser. Voilà à quoi ça ressemble :

public class BlogManagerTests
{
    [Fake] public IBlogPostRepository BlogPostRepository { get; set; }
    [Fake] public ISocialNetworkNotifier SocialNetworkNotifier { get; set; }
    [Fake] public ITimeService TimeService { get; set; }
 
    [UnderTest] public BlogManager BlogManager { get; set; }
 
    public BlogManagerTests()
    {
        Fake.InitializeFixture(this);
    }
 
    [Fact]
    public void NewPost_should_add_blog_post_to_repository()
    {
        var post = A.Dummy<BlogPost>();
 
        BlogManager.NewPost(post);
 
        A.CallTo(() => BlogPostRepository.Add(post)).MustHaveHappened();
    }
 
    [Fact]
    public void PublishPost_should_update_post_in_repository_and_publish_link_to_social_networks()
    {
        var publishDate = DateTimeOffset.Now;
        A.CallTo(() => TimeService.Now).Returns(publishDate);
 
        var post = A.Dummy<BlogPost>();
 
        BlogManager.PublishPost(post);
 
        Assert.Equal(BlogPostStatus.Published, post.Status);
        Assert.Equal(publishDate, post.PublishDate);
 
        A.CallTo(() => BlogPostRepository.Update(post)).MustHaveHappened();
        A.CallTo(() => SocialNetworkNotifier.PublishLink(post)).MustHaveHappened();
    }
}

Le SUT est déclaré comme une propriété avec l’attribut [UnderTest]. Chaque dépendance qu’on a besoin de manipuler est déclarée comme une propriété avec l’attribut [Fake]. La méthode Fake.InitializeFixture initialise le SUT, en créant les dépendances factices à la volée, et affecte ces dépendances aux propriétés correspondantes.

J’aime beaucoup le fait que les tests semblent beaucoup plus propres avec cette technique ; le code de plomberie est réduit au strict minimum, il ne reste plus qu’à configurer les dépendances et écrire les tests proprement dits.

Quelques remarques sur le code ci-dessus :

  • On peut utiliser des champs privés plutôt que des propriétés publiques pour les fakes et le SUT, mais comme ces champs ne sont jamais explicitement assignés dans le code, cela provoque un avertissement du compilateur (CS0649), donc je préfère utiliser des propriétés.
  • Les tests dans mon exemple utilisent xUnit, j’ai donc mis l’appel à Fake.InitializeFixture dans le constructeur, mais si vous utilisez un autre framework comme NUnit ou MSTest, vous pouvez le mettre dans la méthode Setup.

Notez aussi qu’il y a des limites aux scénarios supportés par cette approche :

  • seule l’injection par constructeur est supportée, pas l’injection par propriétés (autrement dit les dépendances doivent être des paramètres du constructeur du SUT)
  • les dépendances nommées ne sont pas supportées ; seul le type est pris en compte, on ne peut donc pas avoir plusieurs dépendances distinctes du même type
  • les collections de dépendances ne sont pas supportées (c’est à dire si votre SUT reçoit une collection de dépendances, par exemple IFooService[])

Utiliser plusieurs sources d’annulation avec CreateLinkedTokenSource

La programmation asynchrone en C# était auparavant quelque chose de difficile ; grâce à la Task Parallel Library de .NET 4 et au async/await de C# 5, elle est devenu relativement facile, et en conséquence, est de plus en plus couramment utilisée. Dans le même temps, une approche standardisée pour gérer l’annulation a été introduite : les jetons d’annulation. L’idée générale est que vous créez un CancellationTokenSource qui contrôle l’annulation, et vous passez le jeton qu’il fournit à la méthode que vous voulez pouvoir annuler. Cette méthode le passera ensuite aux autres méthodes qu’elle appelle si elles peuvent être annulées, et/où vérifiera régulièrement si l’annulation a été demandée. Lors de l’annulation, la méthode lancera une OperationCanceledException. Exemple vite fait mal fait :

private readonly IBusinessService _businessService;
private CancellationTokenSource _cancellationSource;
private Task _asyncOperation;
 
private void StartAsyncOperation()
{
    if (_asyncOperation != null)
        return;
    var _cancellationSource = new CancellationTokenSource();
    _asyncOperation = _businessService.DoSomethingAsync(_cancellationSource.Token);
}
 
// async void is bad; like I said, this is a quick and dirty example
private async void StopAsyncOperation()
{
    try
    {
        _cancellationSource.Cancel();
        // wait for the operation to finish
        await _asyncOperation;
    }
    catch (OperationCanceledException)
    {
        // Operation was successfully canceled
    }
    catch (Exception)
    {
        // Oops, something went wrong
    }
    finally
    {
        _asyncOperation = null;
        _cancellationSource.Dispose();
        _cancellationSource = null;
    }
 
...
 
class BusinessService : IBusinessService
{
    public async Task DoSomethingAsync(CancellationToken cancellationToken)
    {
        var data = await GetDataFromServerAsync(cancellationToken);
        foreach (string line in data)
        {
            cancellationToken.ThrowIfCancellationRequested();
            await ProcessLineAsync(line, cancellationToken);
        }
    }
 
    ...
}

Dans ce cas, StopAsyncOperation serait appelée, par exemple, si l’utilisateur décide d’annuler l’opération.

Tout ça marche très bien et est assez facile à mettre en œuvre. Mais que se passe-t-il s’il y a une autre raison d’annuler l’opération, connue seulement du BusinessService et hors du contrôle de la méthode appelante ? C’est là que la méthode CancellationSource.CreateLinkedTokenSource entre en jeu ; en gros, cette méthode crée une nouvelle source d’annulation qui sera annulée quand l’un des jetons spécifiés sera annulé.

Commençons par un cas simple : vous avez un autre jeton d’annulation que vous voulez aussi prendre en compte. Le code pourrait ressembler à ceci :

public async Task DoSomethingAsync(CancellationToken cancellationToken)
{
    var otherToken = GetOtherCancellationToken();
    using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, otherToken))
    {
        var data = await GetDataFromServerAsync(linkedCts.Token);
        foreach (string line in data)
        {
            linkedCts.Token.ThrowIfCancellationRequested();
            await ProcessLineAsync(line, linkedCts.Token);
        }
    }
}

On crée une source d’annulation liée, basée sur les deux jetons d’annulation, et on utilise le jeton de cette nouvelle source à la place de cancellationToken. Si cancellationToken ou otherToken est annulé, linkedCts.Token sera annulé aussi. Si nécessaire, le code appelant peut détecter comment l’opération a été annulée en vérifiant la propriété CancellationToken de l’exception OperationCanceledException.

Maintenant, un cas un peu plus difficile : la seconde source d’annulation est en fait un évènement. On veut annuler l’opération quand cet évènement se produit, en plus de l’annulation par l’utilisateur représentée par le paramètre cancellationToken. On va donc s’abonner à l’évènement et déclencher l’annulation quand il se produit. Voilà un moyen de le faire :

public async Task DoSomethingAsync(CancellationToken cancellationToken)
{
    using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken))
    {
        EventHandler handler = (sender, e) => linkedCts.Cancel();
        try
        {
            SomeEvent += handler;
            var data = await GetDataFromServerAsync(linkedCts.Token);
            foreach (string line in data)
            {
                linkedCts.Token.ThrowIfCancellationRequested();
                await ProcessLineAsync(line, linkedCts.Token);
            }
        }
        finally
        {
            SomeEvent -= handler;
        }
    }
}

Ici on passe seulement cancellationToken à CreateLinkedTokenSource, et on annule directement linkedCts quand l’évènement se produit. Le code devient un peu plus alambiqué, mais il atteint l’objectif.

Je ne peux pas vraiment vous donner un cas d’utilisation spécifique de cette technique dans le monde réel, parce que les cas où je l’ai utilisée sont trop spécifiques pour être d’intérêt général, mais je peux décrire le scénario général. J’ai une opération de longue durée qui est constituée de plusieurs sous-opérations de longue durée. L’opération entière peut être annulée globalement, et chacune des sous-opérations peut également être annulée individuellement, sans affecter les autres. Voilà à quoi ça peut ressembler (en quasi pseudo-code) :

async Task GlobalOperationAsync(CancellationToken cancellationToken)
{
    foreach (var subOperation is SubOperations)
    {
        cancellationToken.ThrowIfCancellationRequested();
        var subToken = subOperation.GetSpecificCancellationToken();
        using (var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, subToken))
        {
            try
            {
                await subOperation.RunAsync(linkedCts.Token);
            }
            catch (OperationCanceledException ex)
            {
                // Rethrow only if global cancellation was requested
                if (cancellationToken.IsCancellationRequested)
                    throw;
                     
                // otherwise continue running the other sub-operations
            }
        }
    }
}

Remarquez que, bien que CancellationToken ait été introduit avec la TPL et que tous mes exemples soient asynchrones, vous pouvez également utiliser cette technique avec du code synchrone.

Voilà, j’espère que cela vous sera utile. Passez un bon réveillon du nouvel an, et une excellente année 2016 !

[WPF] Empêcher l’utilisateur de coller une image dans un RichTextBox

Le contrôle RichTextBox de WPF est assez puissant, et très pratique quand on a besoin d’accepter une saisie en texte riche. Cependant, l’une de ses fonctionnalités peut devenir problématique : l’utilisateur peut coller une image. Selon ce qu’on veut faire du texte saisi par l’utilisateur, ce n’est pas forcément souhaitable.

Quand j’ai cherché sur le web un moyen d’empêcher cela, les seules solutions que j’ai trouvées suggéraient d’intercepter la frappe de touches Ctrl-V, et de bloquer l’événement si le presse-papiers contient une image. Cette approche présente plusieurs problèmes:

  • elle n’empêche pas l’utilisateur de coller via le menu contextuel
  • elle ne fonctionne pas si le raccourci de la commande a été modifié
  • elle n’empêche pas l’utilisateur d’insérer une image par glisser-déposer

Puisque cette solution ne me convenait pas, j’ai utilisé le site .NET Framework Reference Source pour chercher un moyen d’intercepter l’opération de collage elle-même. J’ai suivi le code à partir de la propriété ApplicationCommands.Paste, et j’ai finalement trouvé l’événement attaché DataObject.Pasting. Ce n’est pas un endroit où j’aurais pensé à chercher, mais quand on y réfléchit, c’est finalement assez logique. Cet événement peut être utilisé pour intercepter une opération de collage ou de glisser-déposer, et permet au gestionnaire de l’événement de faire différentes choses:

  • annuler purement et simplement l’opération
  • changer le format de données qui sera collé depuis le presse-papiers
  • remplacer le DataObject utilisé pour le collage

Dans mon cas, je voulais juste empêcher le collage ou le glisser-déposer d’une image, donc j’ai simplement annulé l’opération quand le FormatToApply était "Bitmap", comme illustré ci-dessous.

XAML:

<RichTextBox DataObject.Pasting="RichTextBox1_Pasting" ... />

Code-behind:

private void RichTextBox1_Pasting(object sender, DataObjectPastingEventArgs e)
{
    if (e.FormatToApply == "Bitmap")
    {
        e.CancelCommand();
    }
}

Bien sûr, il est également possible de gérer ça plus intelligemment. Par exemple, si le DataObject contient plusieurs formats, on pourrait créer un nouveau DataObject avec uniquement les formats acceptables. Comme ça l’utilisateur peut encore coller quelque chose, tant que ce n’est pas une image.

Casse-tête C# n°2

Encore un petit casse-tête basé sur un problème que j’ai rencontré au boulot…

Regardez ce morceau de code :

Console.WriteLine($"x > y is {x > y}");
Console.WriteLine($"!(x <= y) is {!(x <= y)}");

Comment faudrait-il déclarer x et y pour que le programme produise la sortie (apparemment illogique) suivante ?

x > y is False
!(x <= y) is True

Comment récupérer une date en UTC avec SQLite

SQLite est un super moteur de base de données “in-process” : il est très léger, ne nécessite aucun serveur ni configuration, et fonctionne sur toutes les plateformes. Il y a même un provider ADO.NET officiel très bien réalisé. Cependant, si vous stockez des dates en UTC avec ce provider, vous allez probablement rencontrer un sérieux problème : bien que la date soit correctement stockée en UTC (elle est stockée dans un format similaire à ISO8601, avec un ‘Z’ pour indiquer la zone UTC), quand on la relit depuis la base de données, on obtient un DateTime converti en heure locale, avec Kind = Unspecified. Voici un exemple qui illustre le problème (en utilisant Dapper dans LINQPad) :

void Main()
{
    string connectionString = @"Data Source=D:\tmp\testSQLiteDate.db";
    using (var connection = new SQLiteConnection(connectionString))
    {
        connection.Open();
        connection.Execute("create table Foo(Id integer not null primary key, CreationDateUtc datetime not null)");
        
        var foo = new Foo{Id = 42, CreationDateUtc = DateTime.UtcNow};
        foo.Dump("Original object");
        connection.Execute("insert into Foo(Id, CreationDateUtc) values (@Id, @CreationDateUtc)", foo);
        var foo2 = connection.Query<Foo>("select * from Foo where Id = @Id", new{ Id = 42 }).SingleOrDefault();
        foo2.Dump("After reading from DB");
    }
}

class Foo
{
    public int Id { get; set; }
    public DateTime CreationDateUtc { get; set; }
    public DateTimeKind Kind { get { return CreationDateUtc.Kind; } }
}

Voilà le résultat :

image_thumb[1]

Comme vous pouvez le voir, après avoir relu les données depuis la BDD, la date n’est plus en UTC, et il n’y rien qui indique qu’il s’agit d’une date locale. Cela peut causer toutes sortes de bugs si votre code compare des dates qui sont supposées être en UTC.

J’ai d’abord essayé de résoudre le problème dans mon code, en convertissant explicitement la date en UTC, avant de réaliser que la solution était beaucoup plus simple (quoique pas très bien documentée) : il existe un paramètre de la chaine de connexion pour contrôler comment les dates sont gérées. Il suffit de spécifier DateTimeKind=Utc dans la chaine de connexion :

string connectionString = @"Data Source=D:\tmp\testSQLiteDate.db;DateTimeKind=Utc";

Si vous utilisez SqliteConnectionStringBuilder pour construire la chaine de connexion, il faut définir la propriété DateTimeKind à DateTimeKind.Utc.

Le type de date est maintenant préservé :

image_thumb[2]

 

Mise en garde importante : ce paramètre s’applique à toutes les dates lues depuis cette connexion. Si vous avez des dates qui n’étaient pas stockées en UTC, le provider les considèrera quand-même comme UTC, ce qui produira des résultats incorrects. N’utilisez donc ce paramètre que si toutes vos dates sont stockées en UTC.

Créer un conteneur qui crée automatiquement des objets factices avec Unity et FakeItEasy

L’écriture de tests unitaires peut parfois être un peu rébarbative, surtout quand on teste des classes qui ont des dépendances complexes. Heureusement, certains outils rendent cela un peu plus facile. J’utilise beaucoup FakeItEasy ces derniers temps ; c’est un framework de mock pour .NET très facile à utiliser. Il a une API simple et élégante, basée sur les génériques et les expressions lambda, et c’est un vrai plaisir de l’utiliser. Ça a été une bouffée d’air frais par rapport au vieux RhinoMocks que j’utilisais jusqu’ici.

Mais malgré toutes les qualités de FakeItEasy, l’enregistrement des fakes (objets factices) pour les dépendances de la classe testée est encore un peu laborieuse. Ce serait bien si le conteneur IoC pouvait nous créer automatiquement des fakes à la volée, non ? Le code suivant :

var container = new UnityContainer();
 
// Setup dependencies
var fooProvider = A.Fake<IFooProvider>();
container.RegisterInstance(fooProvider);
var barService = A.Fake<IBarService>();
container.RegisterInstance(barService);
var bazManager = A.Fake<IBazManager>();
container.RegisterInstance(bazManager);
 
var sut = container.Resolve<SystemUnderTest>();

Pourrait être réduit à ceci :

var container = new UnityContainer();
 
// This will cause the container to provide fakes for all dependencies
container.AddNewExtension<AutoFakeExtension>();
 
var sut = container.Resolve<SystemUnderTest>();

Eh bien, c’est en fait assez facile à faire avec Unity. Unity n’est pas vraiment le conteneur IoC le plus à la mode, mais il est bien supporté, facile à utiliser, et extensible. J’ai créé l’extension suivante pour permettre le scénario décrit plus haut :

public class AutoFakeExtension : UnityContainerExtension
{
    protected override void Initialize()
    {
        Context.Strategies.AddNew<AutoFakeBuilderStrategy>(UnityBuildStage.PreCreation);
    }
     
    private class AutoFakeBuilderStrategy : BuilderStrategy
    {
        private static readonly MethodInfo _fakeGenericDefinition;
     
        static AutoFakeBuilderStrategy()
        {
            _fakeGenericDefinition = typeof(A).GetMethod("Fake", Type.EmptyTypes);
        }
         
        public override void PreBuildUp(IBuilderContext context)
        {
            if (context.Existing == null)
            {
                var type = context.BuildKey.Type;
                if (type.IsInterface || type.IsAbstract)
                {
                    var fakeMethod = _fakeGenericDefinition.MakeGenericMethod(type);
                    var fake = fakeMethod.Invoke(null, new object[0]);
                    context.PersistentPolicies.Set<ILifetimePolicy>(new ContainerControlledLifetimeManager(), context.BuildKey);
                    context.Existing = fake;
                    context.BuildComplete = true;
                }
            }
            base.PreBuildUp(context);
        }
    }
}

Quelques commentaires sur ce code :

  • La logique est un peu rudimentaire (on génère un fake uniquement pour les interfaces et classes abstraites), mais elle peut facilement être ajustée selon les besoins.
  • Le vilain hack de réflexion est dû au fait que FakeItEasy n’a pas de surcharge non générique pour la méthode A.Fake qui accepte un Type en paramètre. Nul n’est parfait…
  • La durée de vie du fake est définie à “container controlled” (c’est-à-dire qu’elle a la durée de vie du conteneur), parce que si on a besoin de configurer les appels sur le fake, il faut qu’on puisse accéder à la même instance que celle qui est injectée dans la classe testée :
var fooProvider = container.Resolve<IFooProvider>();
A.CallTo(() => fooProvider.GetFoo(42)).Returns(new Foo { Id = 42, Name = “test” });

Remarquez que si on enregistre explicitement une dépendance dans le conteneur, elle sera utilisée en priorité et aucun fake ne sera créé. On peut donc utiliser cette extension tout en gardant la possibilité de spécifier certaines dépendances manuellement :

var container = new UnityContainer();
container.AddNewExtension<AutoFakeExtension>();
container.RegisterType<IFooProvider, TestFooProvider>();
var sut = container.Resolve<SystemUnderTest>();

Bien sûr, cette extension pourrait facilement être modifiée pour utiliser un framework de mock autre que FakeItEasy. Je suppose que le même principe pourrait aussi être appliqué à d’autres conteneurs IoC, pourvu qu’ils aient des points d’extension appropriés.

Et AutoFixture dans tout ça ?

Avant que vous ne posiez la question : oui, je connais AutoFixture. C’est une très bonne librairie, et j’ai aussi essayé de l’utiliser, avec un certain succès. Le code est d’ailleurs très similaire aux exemples ci-dessus. La principale raison pour laquelle je n’ai pas continué à l’utiliser est qu’AutoFixture n’est pas vraiment un conteneur IoC (bien qu’il fasse certaines des choses que fait un conteneur IoC), et je préfère utiliser le même mécanisme d’injection de dépendance dans mes tests unitaires que dans le code applicatif. De plus, je n’étais pas très à l’aise avec la façon dont AutoFixture gère les propriétés quand il crée une instance de la classe à tester. Par défaut, il initialise toutes les propriétés publiques avec des fakes de leur type ; c’est très bien dans le cas des propriétés utilisées pour l’injection de dépendance, mais ça n’a pas de sens pour les autres propriétés. Je sais qu’il est possible d’inhiber ce comportement pour des propriétés spécifiques, mais il faut le faire manuellement au cas par cas ; cela ne prend pas en compte les attributs spécifiques au conteneur IoC, comme [Dependency]. Au final, j’ai trouvé plus simple d’utiliser mon extension Unity personnalisée, dont je comprends et maitrise bien le comportement.

Support de l’asynchronisme et de l’annulation pour les wait handles

Le .NET Framework fournit un certain nombre de primitives de synchronisation bas niveau. Les plus couramment utilisées sont appelées “wait handles”, et héritent de la classe WaitHandle : Semaphore, Mutex, AutoResetEvent et ManualResetEvent. Ces classes existent depuis .NET 2.0 (voire 1.1 pour certaines), mais elles n’ont pas beaucoup évolué depuis, ce qui fait qu’elles ne supportent pas des fonctionnalités introduites plus tard et devenues très courantes. En particulier, elles ne supportent pas l’attente asynchrone, ni l’annulation de l’attente. Heureusement, il est assez facile d’ajouter ces fonctionnalités via des méthodes d’extension.

Annulation

Commençons par le plus facile : l’annulation de l’attente. Dans certains cas, on voudrait pouvoir passer un CancellationToken à WaitHandle.WaitOne, mais aucune des surcharges ne le supporte. Notez que des variantes plus récentes de certaines primitives de synchronisation, comme SemaphoreSlim et ManualResetEventSlim, supportent l’annulation ; cependant elles ne sont pas appropriées dans toutes les situations, car elles sont conçues pour les cas où les temps d’attente sont très courts.

Un CancellationToken expose un WaitHandle, qui est signalé quand l’annulation est demandée. On peut tirer parti de cela pour implémenter l’attente asynchrone sur un wait handle :

public static bool WaitOne(this WaitHandle handle, int millisecondsTimeout, CancellationToken cancellationToken)
{
    int n = WaitHandle.WaitAny(new[] { handle, cancellationToken.WaitHandle }, millisecondsTimeout);
    switch (n)
    {
        case WaitHandle.WaitTimeout:
            return false;
        case 0:
            return true;
        default:
            cancellationToken.ThrowIfCancellationRequested();
            return false; // never reached
    }
}

On utilise WaitHandle.WaitAny pour attendre que le wait handle d’origine ou celui du jeton d’annulation soit signalé. WaitAny renvoie l’index du premier wait handle qui a été signalé, ou  WaitHandle.WaitTimeout si un timeout s’est produit avant que l’un des wait handles ne soit signalé. On a donc 3 résultats possibles :

  • un timeout s’est produit : on renvoie false (comme la méthode WaitOne standard) ;
  • le wait handle d’origine a été signalé en premier : on renvoie true (comme la méthode WaitOne standard) ;
  • le wait handle du jeton d’annulation a été signalé en premier : on lance une OperationCancelledException.

Pour compléter, on peut rajouter quelques surcharges pour les cas d’utilisation courants :

public static bool WaitOne(this WaitHandle handle, TimeSpan timeout, CancellationToken cancellationToken)
{
    return handle.WaitOne((int)timeout.TotalMilliseconds, cancellationToken);
}
 
public static bool WaitOne(this WaitHandle handle, CancellationToken cancellationToken)
{
    return handle.WaitOne(Timeout.Infinite, cancellationToken);
}

Et voilà, on a maintenant une méthode WaitOne qui supporte l’annulation !

Attente asynchrone

Maintenant, comment faire pour attendre un wait handle de façon asynchrone ? C’est un peu plus difficile. Ce qu’on veut ici est une méthode WaitOneAsync qui va renvoyer un Task<bool> (et pendant qu’on y est, autant inclure aussi le support de l’annulation). L’approche habituelle pour créer un wrapper Task pour une opération asynchrone non basée sur une tâche est d’utiliser un TaskCompletionSource<T>, c’est donc ce qu’on va faire. Quand le wait handle sera signalé, on mettra à true le résultat de la tâche; si un timeout se produit, on le mettra à false; et si le jeton d’annulation est signalé, on marquera la tâche comme annulée.

J’ai eu un peu de mal à trouver un moyen d’exécuter un delegate quand un wait handle est signalé, mais j’ai fini par tomber sur la méthode ThreadPool.RegisterWaitForSingleObject, qui sert précisément à ça. Je ne sais pas trop pourquoi elle est dans la classe ThreadPool ; il me semble que ça aurait eu plus de sens de la mettre dans la classe WaitHandle, mais je suppose qu’il y a une bonne raison.

Voilà donc ce qu’on va faire :

  • créer un TaskCompletionSource<bool> ;
  • enregistrer un delegate qui mettra le résultat de la tâche à true quand le wait handle sera signalé, ou à false si un timeout se produit avant, à l’aide de ThreadPool.RegisterWaitForSingleObject ;
  • enregistrer un delegate pour marquer la tâche comme annulée quand le jeton d’annulation sera signalé, en utilisant CancellationToken.Register ;
  • désenregistrer les deux delegates une fois que la tâche est terminée.

Voici l’implémentation :

public static async Task<bool> WaitOneAsync(this WaitHandle handle, int millisecondsTimeout, CancellationToken cancellationToken)
{
    RegisteredWaitHandle registeredHandle = null;
    CancellationTokenRegistration tokenRegistration = default(CancellationTokenRegistration);
    try
    {
        var tcs = new TaskCompletionSource<bool>();
        registeredHandle = ThreadPool.RegisterWaitForSingleObject(
            handle,
            (state, timedOut) => ((TaskCompletionSource<bool>)state).TrySetResult(!timedOut),
            tcs,
            millisecondsTimeout,
            true);
        tokenRegistration = cancellationToken.Register(
            state => ((TaskCompletionSource<bool>)state).TrySetCanceled(),
            tcs);
        return await tcs.Task;
    }
    finally
    {
        if (registeredHandle != null)
            registeredHandle.Unregister(null);
        tokenRegistration.Dispose();
    }
}
 
public static Task<bool> WaitOneAsync(this WaitHandle handle, TimeSpan timeout, CancellationToken cancellationToken)
{
    return handle.WaitOneAsync((int)timeout.TotalMilliseconds, cancellationToken);
}
 
public static Task<bool> WaitOneAsync(this WaitHandle handle, CancellationToken cancellationToken)
{
    return handle.WaitOneAsync(Timeout.Infinite, cancellationToken);
}

Notez que les expressions lambda auraient pu utiliser la variable tcs directement ; cela rendrait le code un peu plus lisible, mais causerait la création d’une closure, donc pour éviter cela et améliorer un peu les performances, on passe tcs via le paramètre state.

On peut maintenant utiliser notre méthode WaitOneAsync de la façon suivante :

var mre = new ManualResetEvent(false);
…
if (await mre.WaitOneAsync(2000, cancellationToken))
{
    …
}

Remarque importante : cette méthode ne fonctionnera pas pour un Mutex, car elle repose sur RegisterWaitForSingleObject, qui d’après la documentation ne fonctionne qu’avec les wait handles autres que Mutex.

Conclusion

On a donc vu qu’avec juste quelques méthodes d’extension, on peut rendre les primitives de synchronisation standard bien plus pratiques à utiliser dans du code moderne qui tire parti de l’asynchronisme et de l’annulation. Cependant je peux difficilement terminer ce billet sans mentionner la librairie AsyncEx de Stephen Cleary ; c’est une boite à outils très complète qui fournit des versions asynchrones de la plupart des primitives de synchronisation, dont certaines qui permettront d’arriver au même résultat que le code ci-dessus. Je vous invite à y jeter un œil, elle contient plein de choses utiles.

Casse-tête C# n°1

J’adore résoudre des casse-têtes en C#; je pense que c’est un excellent moyen d’approfondir sa connaissance du langage. Et en plus, c’est amusant !

Je viens de penser à celui-ci :

static void Test(out int x, out int y)
{
    x = 42;
    y = 123;
    Console.WriteLine (x == y);
}

Que pensez-vous que ce code affiche ? Pouvez-vous en être sûr ? Postez votre réponse dans les commentaires !

J’essaierai de poster plus de casse-têtes à l’avenir, si j’en trouve d’autres.

Test unitaires asynchrones avec NUnit

Récemment, mon équipe et moi avons commencé à écrire des tests unitaires pour une application qui utilise beaucoup de code asynchrone. Nous avons utilisé NUnit (2.6) parce que nous le connaissions déjà bien, mais nous ne l’avions encore jamais utilisé pour tester du code asynchrone.

Supposons que le système à tester soit cette très intéressante classe Calculator :

    public class Calculator
    {
        public async Task<int> AddAsync(int x, int y)
        {
            // simulate long calculation
            await Task.Delay(100).ConfigureAwait(false);
            // the answer to life, the universe and everything.
            return 42;
        }
    }

(Indice: ce code contient un bug… 42 n’est pas toujours la réponse. Ça m’a fait un choc quand j’ai appris ça!)

Et voici un test unitaire pour la méthode AddAsync :

        [Test]
        public async void AddAsync_Returns_The_Sum_Of_X_And_Y()
        {
            var calculator = new Calculator();
            int result = await calculator.AddAsync(1, 1);
            Assert.AreEqual(2, result);
        }

async void vs. async Task

Avant même de lancer ce test, je me suis dit : Ça ne va pas marcher! une méthode async void va retourner immédiatement sur le premier await, NUnit va donc croire que le test est terminé alors que l’assertion n’a pas été exécutée, et le test va donc passer même si l’assertion échoue. J’ai donc changé la signature de la méthode en async Task, en me croyant très malin d’avoir évité ce piège…

        [Test]
        public async Task AddAsync_Returns_The_Sum_Of_X_And_Y()

Comme prévu, le test a échoué, ce qui confirme que NUnit sait gérer les tests asynchrones. J’ai corrigé la classe Calculator, et je n’y ai plus pensé. Jusqu’au jour où j’ai remarqué qu’un collègue écrivait ses tests avec async void. J’ai donc commencé à lui expliquer pourquoi ça ne pouvait pas marcher, et j’ai essayé de le lui démontrer en ajoutant une assertion qui échouerait… et à ma grande surprise, le test a échoué, prouvant que j’avais tort !

Etant d’une nature curieuse, j’ai aussitôt commencé à investiguer… Ma première idée a été de vérifier le SynchronizationContext courant, et en effet, j’ai vu que NUnit l’avant remplacé par une instance de NUnit.Framework.AsyncSynchronizationContext. Cette classe maintient une file des continuations qui sont postées dessus. Après que la méthode async void retourne (c’est-à-dire la première fois qu’on await une tâche qui n’est pas encore terminée), NUnit appelle la méthode WaitForPendingOperationsToComplete, qui exécute toute les continuations de la file, jusqu’à ce que celle-ci soit vide. C’est seulement là que le test sera considéré comme terminé.

La morale de cette histoire est donc que vous pouvez écrire des tests async void avec NUnit 2.6. Cela fonctionne aussi avec les delegates passés à Assert.Throws, qui peuvent avoir le modificateur async. Cela étant dit, ce n’est pas parce que vous pouvez le faire que c’est forcément une bonne idée… Les frameworks de test unitaire n’ont pas tous le même support pour cela, et la prochaine version de NUnit (la 3.0, encore en alpha), ne supportera pas les tests async void.

Donc, à moins que vous ne comptiez rester sur NUnit 2.6.4 ad vitam æternam, il vaut probablement toujours utiliser async Task dans vos tests unitaires.

Optimiser ToArray et ToList en fournissant le nombre d’éléments

Les méthodes d’extension ToArray et ToList sont des moyens pratiques de matérialiser une séquence énumérable (par exemple une requête Linq). Cependant, quelque chose me chiffonne : ces deux méthodes sont très inefficaces si elles ne connaissent pas le nombre d’éléments dans la séquence (ce qui est presque toujours le cas quand on les utilise sur une requête Linq). Concentrons nous sur ToArray pour l’instant (ToList a quelques différences, mais le principe est essentiellement le même).

Pour faire simple, ToArray prend une séquence, et retourne un tableau qui contient tous les éléments de la séquence. Si la séquence implémente ICollection<T>, ToArray utilise la propriété Count pour allouer un tableau de la bonne taille, et copie les éléments dedans ; voici un exemple :

List<User> users = GetUsers();
User[] array = users.ToArray();

Dans ce scénario, ToArray est assez efficace. Maintenant, changeons ce code pour extraire les noms des utilisateurs :

List<User> users = GetUsers();
string[] array = users.Select(u => u.Name).ToArray();

Maintenant, l’argument de ToArray est un IEnumerable<User> renvoyé par Select. Il n’implémente pas ICollection<User>, donc ToArray ne connait pas le nombre d’éléments, et ne peut donc pas allouer un tableau de la bonne taille. Voilà donc ce qu’il fait :

  1. commencer par allouer un petit tableau (4 éléments dans l’implémentation actuelle)
  2. copier les éléments depuis la source vers le tableau jusqu’à ce que celui-ci soit plein
  3. s’il n’y a plus d’éléments dans la séquence, aller en 7
  4. sinon, allouer un nouveau tableau deux fois plus grand que le précédent
  5. copier les éléments de l’ancien tableau vers le nouveau
  6. répéter depuis l’étape 2
  7. si le tableau est plus long que le nombre d’éléments, le tronquer : allouer un nouveau tableau d’exactement la bonne taille, et y copier les éléments depuis le tableau précédent
  8. renvoyer le tableau

S’il n’y a que quelques éléments, tout ceci est assez indolore ; mais pour une très longue séquence, c’est très inefficace, à cause des nombreuses allocations et copies.

Ce qui est agaçant est que, bien souvent, on (le développeur) connait le nombre d’éléments de la source! Dans l’exemple ci-dessus, on n’utilise que Select, qui ne change pas le nombre d’éléments, donc on sait que c’est le même que dans la liste d’origine; mais ToArray ne le sait pas, car l’information a été perdue en cours de route. Si seulement on pouvait fournir cette information nous-mêmes…

Eh bien, c’est en fait très facile à faire : il suffit de créer une nouvelle méthode d’extension qui accepte le nombre d’éléments comme paramètre. Voilà à quoi elle pourrait ressembler :

public static TSource[] ToArray<TSource>(this IEnumerable<TSource> source, int count)
{
    if (source == null) throw new ArgumentNullException("source");
    if (count < 0) throw new ArgumentOutOfRangeException("count");
    var array = new TSource[count];
    int i = 0;
    foreach (var item in source)
    {
        array[i++] = item;
    }
    return array;
}

On peut maintenant optimiser notre exemple précédent de la façon suivante :

List<User> users = GetUsers();
string[] array = users.Select(u => u.Name).ToArray(users.Count);

Notez que si vous spécifiez un nombre inférieur au nombre réel d’éléments dans la séquence, vous obtiendrez une IndexOutOfRangeException ; il est de votre responsabilité de fournir une information correcte à la méthode.

Et au final, qu’est-ce qu’on y gagne? D’après mes tests, cette version améliorée de ToArray est environ deux fois plus rapide que la version standard, pour une longue séquence (testé avec 1.000.000 éléments). Pas mal du tout !

Notez qu’on peut améliorer ToList de la même manière, en utilisant le constructeur de List<T> qui permet de spécifier la capacité initiale :

public static List<TSource> ToList<TSource>(this IEnumerable<TSource> source, int count)
{
    if (source == null) throw new ArgumentNullException("source");
    if (count < 0) throw new ArgumentOutOfRangeException("count");
    var list = new List<TSource>(count);
    foreach (var item in source)
    {
        list.Add(item);
    }
    return list;
}

Ici, le gain de performance n’est pas aussi important que pour ToArray (environ 25% au lieu de 50%), probablement parce que la liste n’a pas besoin d’être tronquée, mais il n’est pas négligeable.

Bien sûr, une optimisation similaire pourrait être faite pour ToDictionary, puisque la classe Dictionary<TKey, TValue> a aussi un constructeur qui permet de spécifier la capacité initiale.

Les méthodes ToArray et ToList améliorées sont disponibles dans ma librairie Linq.Extras, qui fournit également de nombreuses méthodes d’extension utiles pour travailler avec des séquences et collections.