Membuat implementasi antarmuka async

116

Saat ini saya mencoba membuat aplikasi saya menggunakan beberapa metode Async. Semua IO saya dilakukan melalui implementasi eksplisit antarmuka dan saya agak bingung tentang cara membuat operasi asinkron.

Seperti yang saya lihat, saya memiliki dua opsi dalam penerapan:

interface IIO
{
    void DoOperation();
}

OPSI 1: Lakukan implementasi implisit async dan tunggu hasilnya dalam implementasi implisit.

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
}

OPSI2: Lakukan asinkron implementasi eksplisit dan tunggu tugas dari implementasi implisit.

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

    #region IIOAsync Members

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

    #endregion
}

Apakah salah satu dari penerapan ini lebih baik daripada yang lain atau adakah cara lain yang tidak saya pikirkan?

Moriya
sumber

Jawaban:

231

Tak satu pun dari opsi ini benar. Anda mencoba menerapkan antarmuka sinkron secara asinkron. Jangan lakukan itu. Masalahnya adalah ketika DoOperation()kembali, operasi belum selesai. Lebih buruk lagi, jika pengecualian terjadi selama operasi (yang sangat umum dengan operasi IO), pengguna tidak akan memiliki kesempatan untuk menangani pengecualian itu.

Yang perlu Anda lakukan adalah mengubah antarmuka , sehingga menjadi asinkron:

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

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

Dengan cara ini, pengguna akan melihat bahwa operasi tersebut asyncdan mereka akan dapat melakukannya await. Ini juga cukup memaksa pengguna kode Anda untuk beralih async, tetapi itu tidak dapat dihindari.

Juga, saya menganggap penggunaan StartNew()dalam implementasi Anda hanyalah sebuah contoh, Anda seharusnya tidak memerlukannya untuk mengimplementasikan IO asinkron. (Dan new Task()bahkan lebih buruk, yang bahkan tidak akan bekerja, karena Anda tidak Start()yang Task.)

svick.dll
sumber
Bagaimana tampilan ini dengan implementasi eksplisit? Juga, Di mana Anda menunggu implementasi ini?
Moriya
1
Pelaksanaan Explicit @Animal akan terlihat sama seperti biasa (hanya menambahkan async): async Task IIO.DoOperationAsync(). Dan maksud Anda di mana Anda awaitkembali Task? Ke mana pun Anda menelepon DoOperationAsync().
svick
Pada dasarnya saya pikir saya bisa memadatkan pertanyaan saya menjadi "Di mana saya menunggu?" Jika saya tidak menunggu di dalam metode async, saya mendapatkan peringatan kompilasi.
Moriya
1
Idealnya, Anda tidak perlu membungkus kode IO Task.Run(), kode IO itu harus asinkron itu sendiri dan Anda akan melakukannya awaitsecara langsung. Mis line = await streamReader.ReadLineAsync().
svick
4
Maka tidak ada gunanya membuat kode Anda async. Lihat artikel Haruskah saya mengekspos pembungkus asinkron untuk metode sinkron?
svick
19

Solusi yang lebih baik adalah memperkenalkan antarmuka lain untuk operasi asinkron. Antarmuka baru harus mewarisi dari antarmuka asli.

Contoh:

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 Mendesain ulang operasi asinkron Anda sehingga mereka mengembalikan Task, bukan void, kecuali Anda benar-benar harus mengembalikan void.

Dima
sumber
5
Mengapa tidak, GetAwaiter().GetResult()bukan Wait()? Dengan begitu Anda tidak perlu membongkar AggregateExceptionuntuk mengambil pengecualian dalam.
Tagc
Sebuah variasi adalah bergantung pada kelas yang mengimplementasikan beberapa (mungkin eksplisit) interface: class Impl : IIO, IIOAsync. IIO dan IIOAsync sendiri, bagaimanapun, adalah kontrak berbeda yang dapat menghindari penarikan 'kontrak lama' ke kode yang lebih baru. var c = new Impl(); IIOAsync asAsync = c; IIO asSync = c.
pengguna2864740