Rendre les implémentations d'interface asynchrones

116

J'essaie actuellement de créer mon application en utilisant certaines méthodes Async. Toutes mes E / S sont effectuées via des implémentations explicites d'une interface et je suis un peu confus sur la façon de rendre les opérations asynchrones.

Comme je vois les choses, j'ai deux options dans la mise en œuvre:

interface IIO
{
    void DoOperation();
}

OPTION1: Effectuez une implémentation implicite asynchrone et attendez le résultat dans l'implémentation implicite.

class IOImplementation : IIO
{

     async void DoOperation()
    {
        await Task.Factory.StartNew(() =>
            {
                //WRITING A FILE OR SOME SUCH THINGAMAGIG
            });
    }

    #region IIO Members

    void IIO.DoOperation()
    {
        DoOperation();
    }

    #endregion
}

OPTION2: Effectuez une implémentation explicite asynchrone et attendez la tâche de l'implémentation implicite.

class IOAsyncImplementation : IIO
{
    private Task DoOperationAsync()
    {
        return new Task(() =>
            {
                //DO ALL THE HEAVY LIFTING!!!
            });
    }

    #region IIOAsync Members

    async void IIO.DoOperation()
    {
        await DoOperationAsync();
    }

    #endregion
}

L'une de ces implémentations est-elle meilleure que l'autre ou y a-t-il une autre façon de procéder à laquelle je ne pense pas?

Moriya
la source

Réponses:

231

Aucune de ces options n'est correcte. Vous essayez d'implémenter une interface synchrone de manière asynchrone. Ne fais pas ça. Le problème est que lors des DoOperation()retours, l'opération ne sera pas encore terminée. Pire encore, si une exception se produit pendant l'opération (ce qui est très courant avec les opérations d'E / S), l'utilisateur n'aura pas la possibilité de traiter cette exception.

Ce que vous devez faire est de modifier l'interface afin qu'elle soit asynchrone:

interface IIO
{
    Task DoOperationAsync(); // note: no async here
}

class IOImplementation : IIO
{
    public async Task DoOperationAsync()
    {
        // perform the operation here
    }
}

De cette façon, l'utilisateur verra que l'opération est asyncet il pourra le faire await. Cela oblige également à peu près les utilisateurs de votre code à basculer async, mais c'est inévitable.

De plus, je suppose que l'utilisation StartNew()dans votre implémentation n'est qu'un exemple, vous ne devriez pas en avoir besoin pour implémenter des E / S asynchrones. (Et new Task()c'est encore pire, cela ne fonctionnera même pas, car vous ne Start()le faites pas Task.)

svick
la source
À quoi cela ressemblerait-il avec une implémentation explicite? Aussi, où attendez-vous cette implémentation?
Moriya
1
@Animal mise en œuvre explicite regarderait toujours le même ( il suffit d' ajouter async): async Task IIO.DoOperationAsync(). Et voulez-vous dire où êtes-vous awaitle retour Task? Où que vous appeliez DoOperationAsync().
svick
Fondamentalement, je pense que je peux résumer ma question en "Où est-ce que j'attends?" Si je n'attends pas dans la méthode async, j'obtiens des avertissements de compilation.
Moriya
1
Idéalement, vous ne devriez pas avoir besoin d'envelopper le code IO Task.Run(), ce code IO devrait être lui-même asynchrone et vous le feriez awaitdirectement. Par exemple line = await streamReader.ReadLineAsync().
svick
4
Ensuite, il ne sert à rien de rendre votre code asynchrone. Consultez l'article Dois-je exposer des wrappers asynchrones pour les méthodes synchrones?
svick
19

La meilleure solution consiste à introduire une autre interface pour les opérations asynchrones. La nouvelle interface doit hériter de l'interface d'origine.

Exemple:

interface IIO
{
    void DoOperation();
}

interface IIOAsync : IIO
{
    Task DoOperationAsync();
}


class ClsAsync : IIOAsync
{
    public void DoOperation()
    {
        DoOperationAsync().GetAwaiter().GetResult();
    }

    public async Task DoOperationAsync()
    {
        //just an async code demo
        await Task.Delay(1000);
    }
}


class Program
{
    static void Main(string[] args)
    {
        IIOAsync asAsync = new ClsAsync();
        IIO asSync = asAsync;

        Console.WriteLine(DateTime.Now.Second);

        asAsync.DoOperation();
        Console.WriteLine("After call to sync func using Async iface: {0}", 
            DateTime.Now.Second);

        asAsync.DoOperationAsync().GetAwaiter().GetResult();
        Console.WriteLine("After call to async func using Async iface: {0}", 
            DateTime.Now.Second);

        asSync.DoOperation();
        Console.WriteLine("After call to sync func using Sync iface: {0}", 
            DateTime.Now.Second);

        Console.ReadKey(true);
    }
}

PS Reconcevez vos opérations asynchrones pour qu'elles renvoient Task au lieu de void, sauf si vous devez vraiment retourner void.

Dima
la source
5
Pourquoi pas GetAwaiter().GetResult()au lieu de Wait()? De cette façon, vous n'avez pas besoin de décompresser un AggregateExceptionpour récupérer l'exception interne.
Tagc
Une variante est repose sur les interfaces multiples de mise en œuvre de la classe (peut - être explicites): class Impl : IIO, IIOAsync. Cependant, IIO et IIOAsync eux-mêmes sont des contrats différents qui peuvent éviter de faire entrer les «anciens contrats» dans un code plus récent. var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
user2864740