Создание асинхронных реализаций интерфейса

118

В настоящее время я пытаюсь создать свое приложение, используя некоторые методы Async. Все мои операции ввода-вывода выполняются с помощью явных реализаций интерфейса, и я немного не понимаю, как сделать операции асинхронными.

Насколько я понимаю, у меня есть два варианта реализации:

interface IIO
{
    void DoOperation();
}

ВАРИАНТ 1. Выполните неявную реализацию async и дождитесь результата в неявной реализации.

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
}

ВАРИАНТ 2. Выполните явную асинхронную реализацию и дождитесь выполнения задачи из неявной реализации.

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

    #region IIOAsync Members

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

    #endregion
}

Одна из этих реализаций лучше другой или есть другой способ, о котором я не думаю?

Мория
источник

Ответы:

233

Ни один из этих вариантов не подходит. Вы пытаетесь реализовать синхронный интерфейс асинхронно. Не делай этого. Проблема в том, что при DoOperation()возврате операция еще не завершится. Хуже того, если во время операции возникает исключение (что очень часто бывает с операциями ввода-вывода), у пользователя не будет возможности справиться с этим исключением.

Что вам нужно сделать, так это изменить интерфейс , чтобы он был асинхронным:

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

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

Таким образом, пользователь увидит, что операция есть, asyncи сможет ее выполнить await. Это также в значительной степени вынуждает пользователей вашего кода переключаться на async, но это неизбежно.

Кроме того, я предполагаю, что использование StartNew()в вашей реализации является всего лишь примером, вам не нужно это для реализации асинхронного ввода-вывода. (А new Task()еще хуже, что даже не будет работать, потому что вы не .)Start()Task

svick
источник
Как бы это выглядело при явной реализации? Кроме того, где вы ждете этой реализации?
Мория
1
@Animal Явная реализация будет выглядеть так же , как всегда (просто добавить async): async Task IIO.DoOperationAsync(). И вы имеете в виду, откуда вы awaitвернулись Task? Куда бы вы ни позвонили DoOperationAsync().
svick
В принципе, я думаю, что могу сформулировать свой вопрос так: "Где мне ждать?" Если я не жду внутри метода async, я получаю предупреждения компиляции.
Мория
1
В идеале вам не нужно оборачивать код ввода-вывода Task.Run(), этот код ввода-вывода должен быть асинхронным, и вы должны это делать awaitнапрямую. Напр line = await streamReader.ReadLineAsync().
svick
4
Тогда нет особого смысла делать ваш код асинхронным. См. Статью Следует ли предоставлять асинхронные оболочки для синхронных методов?
svick
20

Лучшее решение - ввести другой интерфейс для асинхронных операций. Новый интерфейс должен быть унаследован от исходного интерфейса.

Пример:

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 Измените дизайн своих асинхронных операций, чтобы они возвращали Task вместо void, если только вы действительно не должны возвращать void.

Дима
источник
5
Почему не GetAwaiter().GetResult()вместо Wait()? Таким образом, вам не нужно распаковывать, AggregateExceptionчтобы получить внутреннее исключение.
Tagc
Вариант является полагаться на многократном класс реализации (возможно , явные) интерфейсы: class Impl : IIO, IIOAsync. Однако сами IIO и IIOAsync представляют собой разные контракты, которые позволяют избежать втягивания «старых контрактов» в новый код. var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
user2864740
-3

Вместо интерфейса можно использовать абстрактный класс (в C # 7.3).

// Like interface
abstract class IIO
{
    public virtual async Task<string> DoOperation(string Name)
    {
        throw new NotImplementedException(); // throwing exception
        // return await Task.Run(() => { return ""; }); // or empty do
    }
}

// Implementation
class IOImplementation : IIO
{
    public override async Task<string> DoOperation(string Name)
    {
        return await await Task.Run(() =>
        {
            if(Name == "Spiderman")
                return "ok";
            return "cancel";
        }); 
    }
}
Alatey
источник
1
Какая польза от этого? Почему IIO.DoOperation()вместо abstractсамой себя есть фиктивная реализация ?
БЕКОН
В C # 7.3 я не могу использовать «абстрактный асинхронный» метод без тела.
Алатей
В этом нет никакого смысла. И нет смысла создавать класс, abstractесли нет abstractчленов. DoOperation()Метод может быть сделано abstract, но тогда у вас есть что - то , что, по существу , только интерфейс. Это возвращает нас к тому, почему бы просто не использовать интерфейс? Приведенный выше код бессмыслен. :(
Питер Дунихо
2
«Я не могу использовать« абстрактный асинхронный »метод без тела» - нет причин хотеть использовать abstract async. Единственное, что asyncделает добавление к объявлению метода, - это возможность использовать его awaitв методе. Если ваш метод не используется await, значит, вам не нужно async. Вот почему в первую очередь работает интерфейсный подход; что действительно важно, так это возвращаемый тип . Вы можете сделать возвращаемый тип Task<string>без asyncключевого слова.
Питер Дунихо