web-dev-qa-db-fra.com

Rendre les implémentations d'interface asynchrones

J'essaie actuellement de créer mon application à l'aide de méthodes asynchrones. Tout mon IO se fait via des implémentations explicites d'une interface et je suis un peu confus quant à la manière de rendre les opérations asynchrones.

Lorsque je vois des choses, je dispose de deux options pour 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 la mise en œuvre explicite de manière asynchrone et attendez la tâche de la mise en oeuvre 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 faire que je ne songe pas?

100
Moriya

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 lorsque DoOperation() revient, l'opération ne sera pas encore terminée. Pire, si une exception se produit pendant l'opération (ce qui est très courant avec les opérations IO)), l'utilisateur n'aura aucune chance de gérer cette exception.

Ce que vous devez faire est de modifier l'interface, de sorte 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 async et il sera en mesure de le await le. Cela oblige également les utilisateurs de votre code à passer à async, mais c'est inévitable.

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

200
svick

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

P.S. Modifiez vos opérations asynchrones afin qu'elles renvoient Task au lieu de void, à moins que vous ne deviez vraiment retourner void.

14
Dima