Здійснення асинхронізації інтерфейсу


116

На даний момент я намагаюся зробити свою програму за допомогою деяких методів Async. Весь мій IO робиться за допомогою явних реалізацій інтерфейсу, і я трохи розгублений у тому, як зробити операції асинхронними.

Як я бачу, у реалізації є два варіанти:

interface IIO
{
    void DoOperation();
}

ВАРІАНТ1: Асинхронізуйте неявну реалізацію та чекайте результату в неявній реалізації.

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
}

Чи одна з цих реалізацій краща за іншу чи є інший шлях, про який я не думаю?

Відповіді:


231

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

Що вам потрібно зробити, це змінити інтерфейс , щоб він був асинхронним:

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

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

Таким чином, користувач побачить, що операція є, asyncі вони зможуть awaitїї виконати . Це також змушує користувачів вашого коду перейти async, але це неминуче.

Крім того, я припускаю, що використання StartNew()у вашій реалізації є лише прикладом, вам це не потрібно, щоб реалізувати асинхронний IO. (А new Task()ще гірше, що навіть не буде працювати, тому що ви не .)Start()Task


Як би це виглядало з явною реалізацією? Крім того, де ви чекаєте цієї реалізації?
Морія

1
@Animal Явна реалізація буде виглядати так само , як завжди (просто додати async): async Task IIO.DoOperationAsync(). А ти маєш на увазі, куди ти awaitповернувся Task? Куди б ви не дзвонили DoOperationAsync().
svick

В основному я думаю, що я можу сформулювати своє запитання на "Де я чекаю?" Якщо я не чекаю всередині методу async, я отримую попередження про компіляцію.
Морія

1
В ідеалі вам не потрібно загортати код IO в тому Task.Run(), що код IO повинен бути сам по собі асинхронним, і ви б awaitце зробили безпосередньо. Напр line = await streamReader.ReadLineAsync().
svick

4
Тоді немає особливого сенсу робити ваш код асинхронним. Дивіться статтю Чи слід виставляти асинхронні обгортки для синхронних методів?
svick

19

Краще рішення - ввести ще один інтерфейс для операцій з асинхронізацією. Новий інтерфейс повинен успадкувати від оригінального інтерфейсу.

Приклад:

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 Переробіть свої операції з асинхронізацією, щоб вони повернули Завдання замість недійсних, якщо ви справді не повинні повернути недійсність.


5
Чому б не GetAwaiter().GetResult()замість цього Wait()? Таким чином вам не потрібно розпаковувати AggregateExceptionвнутрішній виняток.
Tagc

Варіант є покладатися на багаторазовому клас реалізації (можливо , явні) інтерфейси: class Impl : IIO, IIOAsync. Однак самі IIO та IIOAsync - це різні контракти, які можуть уникнути введення «старих контрактів» у новіший код. var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
користувач2864740
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.