Asynchroniczne implementacje interfejsu

116

Obecnie próbuję utworzyć moją aplikację przy użyciu niektórych metod Async. Wszystkie moje IO są wykonywane przez jawne implementacje interfejsu i jestem trochę zdezorientowany, jak sprawić, by operacje były asynchroniczne.

Jak widzę rzeczy mam dwie opcje w realizacji:

interface IIO
{
    void DoOperation();
}

OPCJA 1: wykonaj niejawną implementację asynchroniczną i poczekaj na wynik w niejawnej implementacji.

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
}

OPCJA 2: wykonaj jawną implementację asynchroniczną i poczekaj na zadanie z niejawnej implementacji.

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

    #region IIOAsync Members

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

    #endregion
}

Czy jedna z tych implementacji jest lepsza od drugiej, czy też jest inna droga, o której nie myślę?

Moriya
źródło

Odpowiedzi:

231

Żadna z tych opcji nie jest poprawna. Próbujesz zaimplementować interfejs synchroniczny asynchronicznie. Nie rób tego. Problem polega na tym, że po DoOperation()powrocie operacja nie zostanie jeszcze zakończona. Co gorsza, jeśli podczas operacji wystąpi wyjątek (co jest bardzo częste w przypadku operacji IO), użytkownik nie będzie miał szansy poradzić sobie z tym wyjątkiem.

To, co musisz zrobić, to zmodyfikować interfejs , aby był asynchroniczny:

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

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

W ten sposób użytkownik zobaczy, że operacja jest wykonywana asynci będzie mógł to awaitzrobić. To również zmusza użytkowników twojego kodu do przejścia na async, ale jest to nieuniknione.

Zakładam również, że użycie StartNew()w twojej implementacji jest tylko przykładem, nie powinieneś tego potrzebować do implementacji asynchronicznego IO. (A new Task()nawet gorzej, że nie będzie pracować, ponieważ nie ).Start()Task

svick
źródło
Jak by to wyglądało z jawną implementacją? Ponadto, gdzie czekasz na tę realizację?
Moriya
1
@Animal Jawna implementacja będzie wyglądać tak samo jak zawsze (wystarczy dodać async) async Task IIO.DoOperationAsync(). A to znaczy, skąd awaitzwrócony Task? Gdziekolwiek zadzwonisz DoOperationAsync().
svick
Zasadniczo myślę, że mogę sprowadzić moje pytanie do pytania „Na co czekam?” Jeśli nie czekam w metodzie async, otrzymuję ostrzeżenia dotyczące kompilacji.
Moriya
1
W idealnym przypadku nie powinno być potrzeby zawijania kodu IO Task.Run(), ten kod IO powinien być sam asynchroniczny i zrobiłbyś awaitto bezpośrednio. Np line = await streamReader.ReadLineAsync().
svick
4
W takim razie asynchronizacja kodu nie ma większego sensu. Zobacz artykuł Czy należy uwidaczniać otoki asynchroniczne dla metod synchronicznych?
svick
19

Lepszym rozwiązaniem jest wprowadzenie innego interfejsu dla operacji asynchronicznych. Nowy interfejs musi dziedziczyć z oryginalnego interfejsu.

Przykład:

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 Przeprojektuj swoje operacje asynchroniczne, aby zwracały Task zamiast void, chyba że naprawdę musisz zwrócić void.

Dima
źródło
5
Dlaczego nie GetAwaiter().GetResult()zamiast Wait()? W ten sposób nie musisz rozpakowywać an, AggregateExceptionaby pobrać wyjątek wewnętrzny.
Tagc
Odmianą jest polegać na klasie realizacji wielu (ewentualnie Explicit) interfejsy: class Impl : IIO, IIOAsync. Same IIO i IIOAsync są jednak różnymi umowami, dzięki czemu można uniknąć wciągania „starych umów” do nowszego kodu. var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
user2864740