Rendere asincrone le implementazioni dell'interfaccia


116

Attualmente sto cercando di creare la mia applicazione utilizzando alcuni metodi asincroni. Tutto il mio IO viene eseguito tramite implementazioni esplicite di un'interfaccia e sono un po 'confuso su come rendere le operazioni asincrone.

Come vedo le cose ho due opzioni nell'implementazione:

interface IIO
{
    void DoOperation();
}

OPZIONE1: eseguire un'implementazione implicita asincrona e attendere il risultato nell'implementazione implicita.

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
}

OPZIONE2: esegui l'implementazione esplicita in modo asincrono e attendi l'attività dall'implementazione implicita.

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

    #region IIOAsync Members

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

    #endregion
}

Una di queste implementazioni è migliore dell'altra o c'è un'altra strada da percorrere a cui non sto pensando?

Risposte:


231

Nessuna di queste opzioni è corretta. Stai cercando di implementare un'interfaccia sincrona in modo asincrono. Non farlo. Il problema è che quando DoOperation()ritorna, l'operazione non sarà ancora completata. Peggio ancora, se si verifica un'eccezione durante l'operazione (che è molto comune con le operazioni di I / O), l'utente non avrà la possibilità di gestire tale eccezione.

Quello che devi fare è modificare l'interfaccia , in modo che sia asincrona:

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

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

In questo modo, l'utente vedrà che l'operazione è avvenuta asynce sarà in grado di awaitfarlo. Questo costringe anche gli utenti del tuo codice a passare a async, ma è inevitabile.

Inoltre, presumo che l'utilizzo StartNew()nella tua implementazione sia solo un esempio, non dovresti averne bisogno per implementare IO asincrono. (Ed new Task()è ancora peggio, che non sarà nemmeno il lavoro, perché non Start()la Task.)


Come sarebbe con un'implementazione esplicita? Inoltre, dove attendi questa implementazione?
Moriya

1
@Animal implementazione esplicita apparirebbe lo stesso di sempre (basta aggiungere async): async Task IIO.DoOperationAsync(). E vuoi dire dove sei awaittornato Task? Ovunque tu chiami DoOperationAsync().
svick

Fondamentalmente penso di poter condensare la mia domanda in "Dove aspetto?" Se non attendo nel metodo asincrono ricevo avvisi di compilazione.
Moriya

1
Idealmente, non dovresti aver bisogno di avvolgere il codice IO Task.Run(), quel codice IO dovrebbe essere asincrono stesso e lo faresti awaitdirettamente. Ad esempio line = await streamReader.ReadLineAsync().
svick

4
Quindi non ha molto senso rendere il codice asincrono. Vedere l'articolo Devo esporre wrapper asincroni per metodi sincroni?
svick

19

La soluzione migliore è introdurre un'altra interfaccia per le operazioni asincrone. La nuova interfaccia deve ereditare dall'interfaccia originale.

Esempio:

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 Ridisegna le tue operazioni asincrone in modo che restituiscano Task invece di void, a meno che tu non debba veramente restituire void.


5
Perché no GetAwaiter().GetResult()invece di Wait()? In questo modo non è necessario decomprimere un file AggregateExceptionper recuperare l'eccezione interna.
Tagc

Una variante è basano sulla classe che implementa più interfacce (eventualmente espliciti): class Impl : IIO, IIOAsync. IIO e IIOAsync stessi, tuttavia, sono contratti diversi che possono evitare di inserire "vecchi contratti" in codice più recente. var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
user2864740
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.