Hacer implementaciones de interfaz asincrónicas

116

Actualmente estoy tratando de hacer mi aplicación usando algunos métodos Async. Todo mi IO se realiza a través de implementaciones explícitas de una interfaz y estoy un poco confundido acerca de cómo hacer que las operaciones sean asincrónicas.

Como veo las cosas tengo dos opciones en la implementación:

interface IIO
{
    void DoOperation();
}

OPCIÓN1: Realice una implementación implícita asíncrona y espere el resultado en la implementación implícita.

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
}

OPCIÓN 2: Realice la implementación explícita de forma asíncrona y espere la tarea de la implementación implícita.

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

    #region IIOAsync Members

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

    #endregion
}

¿Es una de estas implementaciones mejor que la otra o hay otro camino por recorrer en el que no estoy pensando?

Moriya
fuente

Respuestas:

231

Ninguna de estas opciones es correcta. Está intentando implementar una interfaz síncrona de forma asincrónica. No hagas eso. El problema es que cuando DoOperation()regrese, la operación aún no estará completa. Peor aún, si ocurre una excepción durante la operación (que es muy común con las operaciones de E / S), el usuario no tendrá la oportunidad de lidiar con esa excepción.

Lo que debe hacer es modificar la interfaz , para que sea asincrónica:

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

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

De esta forma, el usuario verá que la operación está asyncy podrá awaithacerlo. Esto también obliga a los usuarios de su código a cambiar async, pero eso es inevitable.

Además, supongo que usar StartNew()en su implementación es solo un ejemplo, no debería necesitarlo para implementar IO asincrónico. (Y new Task()es aún peor, que ni siquiera el trabajo, porque no Start()la Task.)

svick
fuente
¿Cómo se vería esto con una implementación explícita? Además, ¿dónde espera esta implementación?
Moriya
1
Aplicación explícita @Animal se vería igual que siempre (sólo tiene que añadir async): async Task IIO.DoOperationAsync(). ¿Y te refieres a dónde awaitlos regresaste Task? Dondequiera que llames DoOperationAsync().
svick
Básicamente, creo que puedo confiar mi pregunta en "¿Dónde espero?" Si no espero dentro del método asincrónico, recibo advertencias de compilación.
Moriya
1
Idealmente, no debería necesitar ajustar el código IO Task.Run(), ese código IO debería ser asíncrono en sí mismo y lo haría awaitdirectamente. Ej line = await streamReader.ReadLineAsync().
svick
4
Entonces no tiene mucho sentido hacer que su código sea asincrónico. Consulte el artículo ¿Debo exponer contenedores asíncronos para métodos síncronos?
svick
19

Una mejor solución es introducir otra interfaz para operaciones asíncronas. La nueva interfaz debe heredar de la interfaz original.

Ejemplo:

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

PD: Rediseñe sus operaciones asincrónicas para que devuelvan Task en lugar de void, a menos que realmente deba devolver void.

Dima
fuente
5
¿Por qué no en GetAwaiter().GetResult()lugar de Wait()? De esa manera, no es necesario descomprimir un AggregateExceptionpara obtener la excepción interna.
Tagc
Una variación es confiar en la clase que implementa varias interfaces (posiblemente explícitos): class Impl : IIO, IIOAsync. IIO y IIOAsync en sí mismos, sin embargo, son contratos diferentes que pueden evitar incluir "contratos antiguos" en un código más nuevo. var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
user2864740