Tornar as implementações de interface assíncronas

116

No momento, estou tentando fazer meu aplicativo usando alguns métodos Async. Todo o meu IO é feito por meio de implementações explícitas de uma interface e estou um pouco confuso sobre como tornar as operações assíncronas.

A meu ver, tenho duas opções de implementação:

interface IIO
{
    void DoOperation();
}

OPÇÃO1: Faça uma implementação implícita assíncrona e aguarde o resultado na implementação 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
}

OPÇÃO2: Faça a implementação explícita de forma assíncrona e aguarde a tarefa da implementação 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
}

Uma dessas implementações é melhor do que a outra ou há outro caminho a seguir que não estou pensando?

Moriya
fonte

Respostas:

231

Nenhuma dessas opções está correta. Você está tentando implementar uma interface síncrona de forma assíncrona. Não faça isso. O problema é que quando DoOperation()retorna, a operação ainda não estará concluída. Pior, se uma exceção acontecer durante a operação (o que é muito comum com operações de E / S), o usuário não terá a chance de lidar com essa exceção.

O que você precisa fazer é modificar a interface , para que seja assíncrona:

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

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

Desta forma, o usuário verá que a operação está asynce poderá awaitfazê - lo. Isso também força os usuários do seu código a mudar para async, mas isso é inevitável.

Além disso, presumo que o uso StartNew()em sua implementação seja apenas um exemplo, você não deve precisar disso para implementar IO assíncrono. (E new Task()é ainda pior, isso nem vai funcionar, porque você não Start()o faz Task.)

Svick
fonte
Como isso ficaria com uma implementação explícita? Além disso, onde você espera esta implementação?
Moriya
1
Implementação explícita @Animal seria a mesma de sempre (basta adicionar async): async Task IIO.DoOperationAsync(). E você quer dizer onde você awaitvoltou Task? Para onde você ligar DoOperationAsync().
svick
Basicamente, acho que posso conduzir minha pergunta para "Onde estou esperando?" Se eu não esperar dentro do método assíncrono, recebo avisos de compilação.
Moriya
1
Idealmente, você não deve precisar envolver o código IO Task.Run(), esse código IO deve ser assíncrono e você faria awaitisso diretamente. Ex line = await streamReader.ReadLineAsync().
svick
4
Então não há muito sentido em tornar seu código assíncrono. Consulte o artigo Devo expor wrappers assíncronos para métodos síncronos?
svick
19

A melhor solução é apresentar outra interface para operações assíncronas. A nova interface deve ser herdada da interface original.

Exemplo:

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 Redesenhe suas operações assíncronas para que retornem Task em vez de void, a menos que você realmente precise retornar void.

Dima
fonte
5
Por que não em GetAwaiter().GetResult()vez de Wait()? Dessa forma, você não precisa descompactar um AggregateExceptionpara buscar a exceção interna.
Tagc
Uma variação é contar com o múltiplo classe implementação de interfaces (possivelmente explícitas): class Impl : IIO, IIOAsync. IIO e IIOAsync, entretanto, são contratos diferentes que podem evitar a inclusão de 'contratos antigos' em códigos mais novos. var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
user2864740